fix: 添加工作流

This commit is contained in:
root
2025-07-12 12:45:21 +08:00
parent bc19461d8a
commit 81035caf0e
14 changed files with 1830 additions and 48 deletions

View File

@@ -11,6 +11,7 @@ import typer
# 导入命令模块
from python_core.cli.commands import scene_app
from python_core.cli.commands.jsonrpc_server import jsonrpc_app
app = typer.Typer(
name="mixvideo",
@@ -28,13 +29,15 @@ app = typer.Typer(
mixvideo scene batch-detect /videos # 批量检测
mixvideo scene split video.mp4 # 分割视频
mixvideo scene info video.mp4 # 视频信息
mixvideo jsonrpc start # 启动JSON-RPC服务器
""",
rich_markup_mode="rich",
no_args_is_help=True
)
# 添加场景检测命令组到主应用
# 添加命令组到主应用
app.add_typer(scene_app, name="scene")
app.add_typer(jsonrpc_app, name="jsonrpc")
@app.command()
def init():

View File

@@ -0,0 +1,303 @@
#!/usr/bin/env python3
"""
JSON-RPC Server Command
JSON-RPC 服务器命令
Provides HTTP and WebSocket JSON-RPC server for scene detection services.
"""
from pathlib import Path
from typing import Optional
import signal
import sys
import typer
from python_core.cli.const import console
from python_core.utils.jsonrpc_server import JSONRPCServer, JSONRPCWebSocketServer, ServerConfig
# 创建子应用
jsonrpc_app = typer.Typer(help="🌐 JSON-RPC 服务器")
@jsonrpc_app.command()
def start(
host: str = typer.Option("localhost", help="🌐 服务器主机地址"),
port: int = typer.Option(8080, help="🔌 服务器端口"),
debug: bool = typer.Option(False, help="🐛 启用调试模式"),
cors: bool = typer.Option(True, help="🔗 启用CORS支持"),
websocket: bool = typer.Option(False, help="🔌 启用WebSocket服务器"),
max_request_size: int = typer.Option(1024*1024, help="📦 最大请求大小(字节)")
):
"""🚀 启动JSON-RPC服务器"""
config = ServerConfig(
host=host,
port=port,
debug=debug,
cors_enabled=cors,
max_request_size=max_request_size
)
console.print(f"🚀 [bold blue]启动JSON-RPC服务器[/bold blue]")
console.print(f"📍 地址: {host}:{port}")
console.print(f"🔧 模式: {'WebSocket' if websocket else 'HTTP'}")
console.print(f"🐛 调试: {'启用' if debug else '禁用'}")
console.print(f"🔗 CORS: {'启用' if cors else '禁用'}")
# 导入并注册所有JSON-RPC方法
console.print("📋 注册JSON-RPC方法...")
try:
# 导入场景检测模块以注册方法
from python_core.cli.scene_detect import detector
console.print("✅ 场景检测方法已注册")
except Exception as e:
console.print(f"⚠️ 注册方法时出错: {e}")
try:
if websocket:
# WebSocket服务器
import asyncio
server = JSONRPCWebSocketServer(config)
# 注册信号处理
def signal_handler(sig, frame):
console.print("\n🛑 [yellow]收到停止信号,正在关闭服务器...[/yellow]")
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
# 启动异步服务器
asyncio.run(server.start())
else:
# HTTP服务器
server = JSONRPCServer(config)
# 注册信号处理
def signal_handler(sig, frame):
console.print("\n🛑 [yellow]收到停止信号,正在关闭服务器...[/yellow]")
server.stop()
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
# 启动服务器
server.start(blocking=True)
except Exception as e:
console.print(f"❌ [red]服务器启动失败: {e}[/red]")
raise typer.Exit(1)
@jsonrpc_app.command()
def test(
host: str = typer.Option("localhost", help="🌐 服务器主机地址"),
port: int = typer.Option(8080, help="🔌 服务器端口"),
method: str = typer.Option("scene.detect", help="🎯 测试方法名"),
video_path: Optional[str] = typer.Option(None, help="📹 测试视频路径")
):
"""🧪 测试JSON-RPC服务器"""
import requests
import json
if not video_path:
video_path = "assets/1/1752032011698.mp4" # 默认测试视频
console.print(f"🧪 [bold blue]测试JSON-RPC服务器[/bold blue]")
console.print(f"📍 服务器: http://{host}:{port}")
console.print(f"🎯 方法: {method}")
console.print(f"📹 视频: {video_path}")
# 准备测试请求
test_requests = {
"scene.detect": {
"video_path": video_path,
"detector_type": "content",
"threshold": 30.0,
"min_scene_length": 1.0
},
"scene.get_video_info": {
"video_path": video_path
},
"scene.detect_workflow": {
"video_path": video_path,
"detector_type": "content",
"threshold": 15.0,
"enable_ai_analysis": False
}
}
params = test_requests.get(method, {"video_path": video_path})
payload = {
"jsonrpc": "2.0",
"method": method,
"params": params,
"id": 1
}
try:
console.print("📤 发送请求...")
response = requests.post(
f"http://{host}:{port}",
json=payload,
headers={'Content-Type': 'application/json'},
timeout=30
)
console.print(f"📥 响应状态: {response.status_code}")
if response.status_code == 200:
result = response.json()
console.print("✅ [green]请求成功[/green]")
console.print("📋 响应内容:")
console.print(json.dumps(result, indent=2, ensure_ascii=False))
else:
console.print(f"❌ [red]请求失败: {response.text}[/red]")
except requests.exceptions.ConnectionError:
console.print(f"❌ [red]无法连接到服务器 http://{host}:{port}[/red]")
console.print("💡 请确保服务器已启动")
raise typer.Exit(1)
except Exception as e:
console.print(f"❌ [red]测试失败: {e}[/red]")
raise typer.Exit(1)
@jsonrpc_app.command()
def methods():
"""📋 列出可用的JSON-RPC方法"""
console.print("📋 [bold blue]可用的JSON-RPC方法[/bold blue]")
console.print("=" * 60)
methods_info = [
{
"method": "scene.detect",
"description": "基础场景检测",
"params": ["video_path", "detector_type?", "threshold?", "min_scene_length?"]
},
{
"method": "scene.detect_workflow",
"description": "LangGraph工作流场景检测",
"params": ["video_path", "detector_type?", "threshold?", "min_scene_length?",
"output_path?", "output_format?", "enable_ai_analysis?"]
},
{
"method": "scene.get_video_info",
"description": "获取视频信息",
"params": ["video_path"]
},
{
"method": "scene.batch_detect",
"description": "批量场景检测",
"params": ["directory", "detector_type?", "threshold?", "min_scene_length?",
"output_dir?", "output_format?"]
}
]
for info in methods_info:
console.print(f"\n🎯 [bold]{info['method']}[/bold]")
console.print(f" 📝 {info['description']}")
console.print(f" 📋 参数: {', '.join(info['params'])}")
console.print("\n💡 [yellow]参数说明:[/yellow]")
console.print(" • ? 表示可选参数")
console.print(" • detector_type: content/threshold/adaptive")
console.print(" • output_format: json/csv/txt")
console.print(" • threshold: 0-100")
@jsonrpc_app.command()
def client_example():
"""📖 显示客户端调用示例"""
console.print("📖 [bold blue]JSON-RPC 客户端调用示例[/bold blue]")
console.print("=" * 60)
examples = [
{
"title": "Python requests 示例",
"code": '''import requests
import json
def call_scene_detect(video_path, threshold=30.0):
payload = {
"jsonrpc": "2.0",
"method": "scene.detect",
"params": {
"video_path": video_path,
"threshold": threshold
},
"id": 1
}
response = requests.post(
"http://localhost:8080",
json=payload,
headers={'Content-Type': 'application/json'}
)
return response.json()
# 调用示例
result = call_scene_detect("video.mp4", 15.0)
print(result)'''
},
{
"title": "curl 命令示例",
"code": '''curl -X POST http://localhost:8080 \\
-H "Content-Type: application/json" \\
-d '{
"jsonrpc": "2.0",
"method": "scene.detect",
"params": {
"video_path": "video.mp4",
"threshold": 15.0
},
"id": 1
}'
'''
},
{
"title": "JavaScript fetch 示例",
"code": '''async function detectScenes(videoPath, threshold = 30.0) {
const response = await fetch('http://localhost:8080', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
jsonrpc: '2.0',
method: 'scene.detect',
params: {
video_path: videoPath,
threshold: threshold
},
id: 1
})
});
return await response.json();
}
// 调用示例
detectScenes('video.mp4', 15.0).then(result => {
console.log(result);
});'''
}
]
for example in examples:
console.print(f"\n📝 [bold]{example['title']}[/bold]")
console.print("```")
console.print(example['code'])
console.print("```")
if __name__ == "__main__":
jsonrpc_app()

View File

@@ -19,7 +19,9 @@ def detect(
threshold: float = typer.Option(30.0, help="🎚️ 检测阈值 (0-100)"),
min_scene_length: float = typer.Option(1.0, help="⏱️ 最小场景长度(秒)"),
output: Optional[Path] = typer.Option(None, "--output", "-o", help="📄 输出文件路径"),
format: str = typer.Option("json", help="📋 输出格式 (json/csv/txt)")
format: str = typer.Option("json", help="📋 输出格式 (json/csv/txt)"),
use_workflow: bool = typer.Option(False, "--workflow", help="🔄 使用LangGraph工作流"),
enable_ai: bool = typer.Option(True, "--ai/--no-ai", help="🧠 启用AI分析")
):
"""🎯 检测单个视频的场景"""
try:
@@ -40,39 +42,97 @@ def detect(
progress_reporter.info("💡 可用格式: json, csv, txt")
raise typer.Exit(1)
# 执行检测
result = scene_detector.detect_scenes(
video_path, detector_type, threshold, min_scene_length
)
if not result.success:
progress_reporter.error(f"❌ 检测失败: {result.error}")
raise typer.Exit(1)
# 显示结果摘要
console.print(f"📊 检测结果摘要:")
console.print(f" 文件: {result.filename}")
console.print(f" 检测器: {result.detector_type}")
console.print(f" 阈值: {result.threshold}")
console.print(f" 场景数: {result.total_scenes}")
console.print(f" 总时长: {result.total_duration:.2f}")
console.print(f" 检测时间: {result.detection_time:.2f}")
# 显示场景详情
if result.scenes:
console.print(f"\n🎬 场景列表:")
for scene in result.scenes[:10]: # 只显示前10个场景
console.print(f" 场景 {scene.index}: {scene.start_time:.2f}s - {scene.end_time:.2f}s ({scene.duration:.2f}s)")
if len(result.scenes) > 10:
console.print(f" ... 还有 {len(result.scenes) - 10} 个场景")
# 保存结果
if output:
scene_detector.save_results(result, output, output_format)
progress_reporter.success(f"📄 结果已保存到: {output}")
return result
# 选择执行方式
if use_workflow:
# 使用LangGraph工作流
progress_reporter.info("🔄 使用LangGraph工作流进行检测...")
workflow_result = scene_detector.detect_with_workflow(
video_path, detector_type, threshold, min_scene_length,
output, output_format, enable_ai
)
result = workflow_result.get("detection_result")
ai_analysis = workflow_result.get("ai_analysis")
video_info = workflow_result.get("video_info")
errors = workflow_result.get("errors", [])
if errors:
for error in errors:
progress_reporter.error(f"{error}")
raise typer.Exit(1)
if not result or not result.success:
progress_reporter.error(f"❌ 工作流检测失败: {result.error if result else '未知错误'}")
raise typer.Exit(1)
# 显示工作流结果
console.print(f"🔄 LangGraph工作流检测完成")
console.print(f"📊 检测结果摘要:")
console.print(f" 文件: {result.filename}")
console.print(f" 检测器: {result.detector_type}")
console.print(f" 阈值: {result.threshold}")
console.print(f" 场景数: {result.total_scenes}")
console.print(f" 总时长: {result.total_duration:.2f}")
console.print(f" 检测时间: {result.detection_time:.2f}")
# 显示视频信息
if video_info:
console.print(f"\n📹 视频信息:")
console.print(f" 分辨率: {video_info.get('resolution', 'Unknown')}")
console.print(f" 帧率: {video_info.get('fps', 0):.2f} fps")
console.print(f" 总帧数: {video_info.get('frame_count', 0)}")
# 显示AI分析结果
if ai_analysis and enable_ai:
console.print(f"\n🧠 AI分析结果:")
console.print(f"{ai_analysis}")
# 显示场景详情
if result.scenes:
console.print(f"\n🎬 场景列表:")
for scene in result.scenes[:10]:
console.print(f" 场景 {scene.index}: {scene.start_time:.2f}s - {scene.end_time:.2f}s ({scene.duration:.2f}s)")
if len(result.scenes) > 10:
console.print(f" ... 还有 {len(result.scenes) - 10} 个场景")
return workflow_result
else:
# 使用传统方法
result = scene_detector.detect_scenes(
video_path, detector_type, threshold, min_scene_length
)
if not result.success:
progress_reporter.error(f"❌ 检测失败: {result.error}")
raise typer.Exit(1)
# 显示结果摘要
console.print(f"📊 检测结果摘要:")
console.print(f" 文件: {result.filename}")
console.print(f" 检测器: {result.detector_type}")
console.print(f" 阈值: {result.threshold}")
console.print(f" 场景数: {result.total_scenes}")
console.print(f" 总时长: {result.total_duration:.2f}")
console.print(f" 检测时间: {result.detection_time:.2f}")
# 显示场景详情
if result.scenes:
console.print(f"\n🎬 场景列表:")
for scene in result.scenes[:10]: # 只显示前10个场景
console.print(f" 场景 {scene.index}: {scene.start_time:.2f}s - {scene.end_time:.2f}s ({scene.duration:.2f}s)")
if len(result.scenes) > 10:
console.print(f" ... 还有 {len(result.scenes) - 10} 个场景")
# 保存结果
if output:
scene_detector.save_results(result, output, output_format)
progress_reporter.success(f"📄 结果已保存到: {output}")
return result
except Exception as e:
progress_reporter.error(f"❌ 命令执行失败: {e}")
@@ -342,3 +402,148 @@ def info(
except Exception as e:
progress_reporter.error(f"❌ 获取视频信息失败: {e}")
raise typer.Exit(1)
@scene_app.command()
def workflow(
video_path: Path = typer.Argument(..., help="📹 视频文件路径", exists=True),
detector: str = typer.Option("content", help="🔧 检测器类型 (content/threshold/adaptive)"),
threshold: float = typer.Option(30.0, help="🎚️ 检测阈值 (0-100)"),
min_scene_length: float = typer.Option(1.0, help="⏱️ 最小场景长度(秒)"),
output: Optional[Path] = typer.Option(None, "--output", "-o", help="📄 输出文件路径"),
format: str = typer.Option("json", help="📋 输出格式 (json/csv/txt)"),
enable_ai: bool = typer.Option(True, "--ai/--no-ai", help="🧠 启用AI分析"),
interactive: bool = typer.Option(False, "--interactive", "-i", help="🔄 交互式工作流")
):
"""🔄 使用LangGraph工作流进行智能场景检测"""
try:
from python_core.cli.scene_detect import detector as scene_detector, DetectorType, OutputFormat
# 验证参数
try:
detector_type = DetectorType(detector)
output_format = OutputFormat(format)
except ValueError as e:
progress_reporter.error(f"❌ 参数错误: {e}")
raise typer.Exit(1)
console.print("🔄 [bold blue]LangGraph智能场景检测工作流[/bold blue]")
console.print("=" * 60)
if interactive:
# 交互式模式
console.print("🎯 交互式模式启动...")
# 确认参数
console.print(f"\n📋 检测参数:")
console.print(f" 视频文件: {video_path}")
console.print(f" 检测器: {detector}")
console.print(f" 阈值: {threshold}")
console.print(f" 最小场景长度: {min_scene_length}")
console.print(f" AI分析: {'启用' if enable_ai else '禁用'}")
if not typer.confirm("\n是否继续执行?"):
console.print("❌ 用户取消操作")
return
# 执行工作流
progress_reporter.info("🚀 启动LangGraph工作流...")
workflow_result = scene_detector.detect_with_workflow(
video_path, detector_type, threshold, min_scene_length,
output, output_format, enable_ai
)
result = workflow_result.get("detection_result")
ai_analysis = workflow_result.get("ai_analysis")
video_info = workflow_result.get("video_info")
workflow_state = workflow_result.get("workflow_state")
errors = workflow_result.get("errors", [])
# 检查错误
if errors:
console.print("\n❌ [red]工作流执行中发现错误:[/red]")
for error in errors:
console.print(f"{error}")
raise typer.Exit(1)
if not result or not result.success:
progress_reporter.error(f"❌ 工作流检测失败: {result.error if result else '未知错误'}")
raise typer.Exit(1)
# 显示完整结果
console.print("\n" + "=" * 60)
console.print("🎉 [bold green]LangGraph工作流执行完成[/bold green]")
console.print("=" * 60)
# 工作流状态
console.print(f"\n🔄 工作流状态: [bold]{workflow_state}[/bold]")
# 视频信息
if video_info:
console.print(f"\n📹 [bold]视频信息[/bold]:")
console.print(f" 文件名: {result.filename}")
console.print(f" 分辨率: {video_info.get('resolution', 'Unknown')}")
console.print(f" 帧率: {video_info.get('fps', 0):.2f} fps")
console.print(f" 总帧数: {video_info.get('frame_count', 0):,}")
console.print(f" 时长: {result.total_duration:.2f}")
# 检测结果
console.print(f"\n🎯 [bold]检测结果[/bold]:")
console.print(f" 检测器类型: {result.detector_type}")
console.print(f" 检测阈值: {result.threshold}")
console.print(f" 场景数量: [bold green]{result.total_scenes}[/bold green]")
console.print(f" 检测耗时: {result.detection_time:.2f}")
# 场景详情
if result.scenes:
console.print(f"\n🎬 [bold]场景详情[/bold]:")
for scene in result.scenes:
duration_color = "green" if scene.duration >= 2.0 else "yellow" if scene.duration >= 1.0 else "red"
console.print(
f" 场景 {scene.index:2d}: "
f"{scene.start_time:6.2f}s - {scene.end_time:6.2f}s "
f"([{duration_color}]{scene.duration:5.2f}s[/{duration_color}])"
)
# AI分析结果
if ai_analysis and enable_ai:
console.print(f"\n🧠 [bold]AI智能分析[/bold]:")
console.print("-" * 50)
console.print(ai_analysis)
console.print("-" * 50)
elif enable_ai:
console.print(f"\n⚠️ AI分析不可用")
# 保存信息
if output:
console.print(f"\n💾 结果已保存到: [bold]{output}[/bold]")
# 交互式后续操作
if interactive:
console.print(f"\n🎯 [bold]后续操作选项[/bold]:")
console.print("1. 保存结果到文件")
console.print("2. 调整参数重新检测")
console.print("3. 分割视频")
console.print("4. 退出")
choice = typer.prompt("请选择操作 (1-4)", type=int, default=4)
if choice == 1 and not output:
output_path = typer.prompt("请输入输出文件路径", type=str)
scene_detector.save_results(result, Path(output_path), output_format)
console.print(f"✅ 结果已保存到: {output_path}")
elif choice == 2:
console.print("🔄 参数调整功能开发中...")
elif choice == 3:
console.print("✂️ 视频分割功能开发中...")
else:
console.print("👋 感谢使用LangGraph工作流")
return workflow_result
except Exception as e:
progress_reporter.error(f"❌ 工作流命令执行失败: {e}")
raise typer.Exit(1)

View File

@@ -1,4 +1,4 @@
from python_core.utils.jsonrpc import create_progress_reporter
from python_core.utils.jsonrpc_enhanced import create_progress_reporter
from rich.console import Console
from python_core.config import settings
console = Console()

View File

@@ -1,23 +1,26 @@
#!/usr/bin/env python3
"""
PySceneDetect 场景检测命令行工具
PySceneDetect 场景检测命令行工具 - LangGraph增强版
"""
import os
import json
import time
from pathlib import Path
from typing import Optional, List
from typing import Optional, List, Literal, Dict, Any
from enum import Enum
from dataclasses import dataclass, asdict
import typer
from python_core.cli.const import progress_reporter, console, project_root
from python_core.cli.const import progress_reporter
# 检查 PySceneDetect 依赖
# PySceneDetect 依赖
from scenedetect import open_video, SceneManager
from scenedetect.detectors import ContentDetector, ThresholdDetector
from scenedetect.video_splitter import split_video_ffmpeg
# LangGraph 依赖
from langgraph.graph import StateGraph, START, END
from langgraph.graph.state import CompiledStateGraph
from langgraph.checkpoint.memory import MemorySaver
from langchain_anthropic import ChatAnthropic
class DetectorType(str, Enum):
"""检测器类型"""
@@ -55,11 +58,59 @@ class DetectionResult:
success: bool
error: Optional[str] = None
# LangGraph 工作流状态
@dataclass
class SceneDetectionWorkflowState:
"""场景检测工作流状态"""
# 输入参数
video_path: str = ""
detector_type: str = "content"
threshold: float = 30.0
min_scene_length: float = 1.0
output_path: Optional[str] = None
output_format: str = "json"
enable_ai_analysis: bool = True
# 工作流状态
current_stage: str = "init"
progress: int = 0
total_steps: int = 5
# 中间结果
video_info: Dict[str, Any] = None
raw_scenes: List[Any] = None
processed_scenes: List[SceneInfo] = None
# 最终结果
detection_result: Optional[DetectionResult] = None
ai_analysis: Optional[str] = None
# 错误处理
errors: List[str] = None
def __post_init__(self):
if self.video_info is None:
self.video_info = {}
if self.raw_scenes is None:
self.raw_scenes = []
if self.processed_scenes is None:
self.processed_scenes = []
if self.errors is None:
self.errors = []
class SceneDetector:
"""场景检测器"""
def __init__(self):
self.supported_formats = {'.mp4', '.avi', '.mov', '.mkv', '.wmv', '.flv', '.webm', '.m4v'}
# 初始化AI分析器如果可用
try:
self.llm = ChatAnthropic(model="claude-3-5-sonnet-20241022")
self.ai_enabled = True
except Exception as e:
progress_reporter.warning(f"⚠️ AI分析器初始化失败: {e}")
self.ai_enabled = False
def detect_scenes(self, video_path: Path, detector_type: DetectorType = DetectorType.CONTENT,
threshold: float = 30.0, min_scene_length: float = 1.0) -> DetectionResult:
@@ -361,5 +412,460 @@ class SceneDetector:
f.write("\n")
# ==================== LangGraph 工作流方法 ====================
def create_detection_workflow(self) -> Optional[CompiledStateGraph]:
"""创建场景检测工作流"""
# 定义工作流节点
def validate_input(state: SceneDetectionWorkflowState) -> Dict[str, Any]:
"""验证输入参数"""
progress_reporter.info("🔍 验证输入参数...")
video_path = Path(state.video_path)
errors = []
# 验证文件存在
if not video_path.exists():
errors.append(f"视频文件不存在: {video_path}")
# 验证文件格式
if video_path.suffix.lower() not in self.supported_formats:
errors.append(f"不支持的文件格式: {video_path.suffix}")
# 验证参数范围
if not (0 <= state.threshold <= 100):
errors.append(f"阈值超出范围 (0-100): {state.threshold}")
if state.min_scene_length < 0:
errors.append(f"最小场景长度不能为负数: {state.min_scene_length}")
return {
"current_stage": "validated" if not errors else "error",
"progress": 1,
"errors": errors
}
def extract_video_info(state: SceneDetectionWorkflowState) -> Dict[str, Any]:
"""提取视频信息"""
progress_reporter.info("📊 提取视频信息...")
try:
import cv2
cap = cv2.VideoCapture(state.video_path)
if not cap.isOpened():
raise Exception("无法打开视频文件")
fps = cap.get(cv2.CAP_PROP_FPS)
frame_count = 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 = frame_count / fps if fps > 0 else 0
cap.release()
video_info = {
"fps": fps,
"frame_count": frame_count,
"width": width,
"height": height,
"duration": duration,
"resolution": f"{width}x{height}"
}
progress_reporter.info(f"📹 视频信息: {video_info['resolution']}, {fps:.2f}fps, {duration:.2f}s")
return {
"current_stage": "info_extracted",
"progress": 2,
"video_info": video_info
}
except Exception as e:
return {
"current_stage": "error",
"errors": state.errors + [f"提取视频信息失败: {e}"]
}
def detect_scenes(state: SceneDetectionWorkflowState) -> Dict[str, Any]:
"""执行场景检测"""
progress_reporter.info("🎯 执行场景检测...")
try:
# 使用现有的检测逻辑
result = self.detect_scenes(
Path(state.video_path),
DetectorType(state.detector_type),
state.threshold,
state.min_scene_length
)
return {
"current_stage": "scenes_detected",
"progress": 3,
"detection_result": result,
"processed_scenes": result.scenes
}
except Exception as e:
return {
"current_stage": "error",
"errors": state.errors + [f"场景检测失败: {e}"]
}
def analyze_with_ai(state: SceneDetectionWorkflowState) -> Dict[str, Any]:
"""AI分析场景结果"""
if not self.ai_enabled or not state.enable_ai_analysis:
progress_reporter.info("⚠️ AI分析已禁用跳过此步骤")
return {
"current_stage": "analysis_skipped",
"progress": 4,
"ai_analysis": "AI分析已禁用"
}
progress_reporter.info("🧠 AI分析场景结果...")
try:
result = state.detection_result
video_info = state.video_info
analysis_prompt = f"""
请分析以下视频场景检测结果:
视频信息:
- 文件: {result.filename}
- 分辨率: {video_info.get('resolution', 'Unknown')}
- 时长: {result.total_duration:.2f}
- 帧率: {video_info.get('fps', 0):.2f}fps
检测结果:
- 检测器: {result.detector_type}
- 阈值: {result.threshold}
- 场景数: {result.total_scenes}
- 检测时间: {result.detection_time:.2f}
场景详情:
{self._format_scenes_for_ai(result.scenes)}
请提供:
1. 场景分布分析
2. 检测质量评估
3. 参数优化建议
4. 潜在问题识别
"""
response = self.llm.invoke([{"role": "user", "content": analysis_prompt}])
return {
"current_stage": "ai_analyzed",
"progress": 4,
"ai_analysis": response.content
}
except Exception as e:
progress_reporter.warning(f"⚠️ AI分析失败: {e}")
return {
"current_stage": "analysis_failed",
"progress": 4,
"ai_analysis": f"AI分析失败: {e}"
}
def finalize_results(state: SceneDetectionWorkflowState) -> Dict[str, Any]:
"""整理最终结果"""
progress_reporter.info("📋 整理最终结果...")
# 保存结果(如果指定了输出路径)
if state.output_path and state.detection_result:
try:
output_path = Path(state.output_path)
output_format = OutputFormat(state.output_format)
self.save_results(state.detection_result, output_path, output_format)
except Exception as e:
progress_reporter.warning(f"⚠️ 保存结果失败: {e}")
return {
"current_stage": "completed",
"progress": 5
}
# 路由函数
def route_next_step(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 handle_error(state: SceneDetectionWorkflowState) -> Dict[str, Any]:
"""处理错误"""
error_msg = "; ".join(state.errors)
progress_reporter.error(f"❌ 工作流错误: {error_msg}")
return {"current_stage": "failed"}
# 构建工作流图
workflow = StateGraph(SceneDetectionWorkflowState)
# 添加节点
workflow.add_node("validate", validate_input)
workflow.add_node("extract_info", extract_video_info)
workflow.add_node("detect", detect_scenes)
workflow.add_node("analyze", analyze_with_ai)
workflow.add_node("finalize", finalize_results)
workflow.add_node("error", handle_error)
# 添加边
workflow.add_edge(START, "validate")
workflow.add_conditional_edges("validate", route_next_step)
workflow.add_conditional_edges("extract_info", route_next_step)
workflow.add_conditional_edges("detect", route_next_step)
workflow.add_conditional_edges("analyze", route_next_step)
workflow.add_edge("finalize", END)
workflow.add_edge("error", END)
# 编译工作流
memory = MemorySaver()
return workflow.compile(checkpointer=memory)
def _format_scenes_for_ai(self, scenes: List[SceneInfo]) -> str:
"""格式化场景信息供AI分析"""
if not scenes:
return "无场景数据"
formatted = []
for scene in scenes[:10]: # 只显示前10个场景
formatted.append(
f"场景 {scene.index}: {scene.start_time:.2f}s - {scene.end_time:.2f}s "
f"(时长: {scene.duration:.2f}s)"
)
if len(scenes) > 10:
formatted.append(f"... 还有 {len(scenes) - 10} 个场景")
return "\n".join(formatted)
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) -> 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 # 使用参数
)
# 执行工作流
config = {"configurable": {"thread_id": f"detection_{int(time.time())}"}}
try:
final_state = workflow.invoke(initial_state, config)
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:
progress_reporter.error(f"❌ 工作流执行失败: {e}")
raise
# ==================== JSON-RPC 方法注册 ====================
def register_jsonrpc_methods(self):
"""注册JSON-RPC方法到全局注册器"""
from python_core.utils.jsonrpc_enhanced import method_registry
# 注册方法到全局注册器
method_registry.register_function(self.jsonrpc_detect_scenes, "scene.detect")
method_registry.register_function(self.jsonrpc_detect_with_workflow, "scene.detect_workflow")
method_registry.register_function(self.jsonrpc_get_video_info, "scene.get_video_info")
method_registry.register_function(self.jsonrpc_batch_detect, "scene.batch_detect")
def jsonrpc_detect_scenes(self, video_path: str, detector_type: str = "content",
threshold: float = 30.0, min_scene_length: float = 1.0) -> Dict[str, Any]:
"""JSON-RPC方法场景检测"""
try:
result = self.detect_scenes(
Path(video_path),
DetectorType(detector_type),
threshold,
min_scene_length
)
return {
"success": result.success,
"filename": result.filename,
"detector_type": result.detector_type,
"threshold": result.threshold,
"total_scenes": result.total_scenes,
"total_duration": result.total_duration,
"detection_time": result.detection_time,
"scenes": [asdict(scene) for scene in result.scenes],
"error": result.error
}
except Exception as e:
return {
"success": False,
"error": str(e)
}
def jsonrpc_detect_with_workflow(self, video_path: str, detector_type: str = "content",
threshold: float = 30.0, min_scene_length: float = 1.0,
output_path: Optional[str] = None, output_format: str = "json",
enable_ai_analysis: bool = True) -> Dict[str, Any]:
"""JSON-RPC方法工作流场景检测"""
try:
output_path_obj = Path(output_path) if output_path else None
result = self.detect_with_workflow(
Path(video_path),
DetectorType(detector_type),
threshold,
min_scene_length,
output_path_obj,
OutputFormat(output_format),
enable_ai_analysis
)
# 序列化结果
serialized_result = {}
for key, value in result.items():
if key == "detection_result" and value:
serialized_result[key] = {
"success": value.success,
"filename": value.filename,
"detector_type": value.detector_type,
"threshold": value.threshold,
"total_scenes": value.total_scenes,
"total_duration": value.total_duration,
"detection_time": value.detection_time,
"scenes": [asdict(scene) for scene in value.scenes],
"error": value.error
}
else:
serialized_result[key] = value
return serialized_result
except Exception as e:
return {
"success": False,
"error": str(e)
}
def get_video_info(self, video_path: Path) -> Dict[str, Any]:
"""获取视频信息"""
try:
import cv2
cap = cv2.VideoCapture(str(video_path))
if not cap.isOpened():
raise Exception("无法打开视频文件")
fps = cap.get(cv2.CAP_PROP_FPS)
frame_count = 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 = frame_count / fps if fps > 0 else 0
cap.release()
return {
"filename": video_path.name,
"fps": fps,
"frame_count": frame_count,
"width": width,
"height": height,
"duration": duration,
"resolution": f"{width}x{height}",
"file_size": video_path.stat().st_size if video_path.exists() else 0
}
except Exception as e:
raise Exception(f"获取视频信息失败: {e}")
def jsonrpc_get_video_info(self, video_path: str) -> Dict[str, Any]:
"""JSON-RPC方法获取视频信息"""
try:
info = self.get_video_info(Path(video_path))
return {
"success": True,
"info": info
}
except Exception as e:
return {
"success": False,
"error": str(e)
}
def jsonrpc_batch_detect(self, directory: str, detector_type: str = "content",
threshold: float = 30.0, min_scene_length: float = 1.0,
output_dir: Optional[str] = None, output_format: str = "json") -> Dict[str, Any]:
"""JSON-RPC方法批量场景检测"""
try:
output_dir_obj = Path(output_dir) if output_dir else None
results = self.batch_detect(
Path(directory),
DetectorType(detector_type),
threshold,
min_scene_length,
output_dir_obj,
OutputFormat(output_format)
)
# 序列化结果
serialized_results = []
for result in results:
serialized_results.append({
"success": result.success,
"filename": result.filename,
"detector_type": result.detector_type,
"threshold": result.threshold,
"total_scenes": result.total_scenes,
"total_duration": result.total_duration,
"detection_time": result.detection_time,
"scenes": [asdict(scene) for scene in result.scenes],
"error": result.error
})
return {
"success": True,
"results": serialized_results,
"total_processed": len(results)
}
except Exception as e:
return {
"success": False,
"error": str(e)
}
# 创建全局检测器实例
detector = SceneDetector()
detector = SceneDetector()
# 注册JSON-RPC方法
detector.register_jsonrpc_methods()