fix: 修复错误
This commit is contained in:
@@ -1,279 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
JSON-RPC Client Demo
|
||||
JSON-RPC 客户端演示
|
||||
|
||||
演示如何使用Python客户端调用场景检测JSON-RPC API
|
||||
"""
|
||||
|
||||
import json
|
||||
import requests
|
||||
import time
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
|
||||
class SceneDetectionClient:
|
||||
"""场景检测JSON-RPC客户端"""
|
||||
|
||||
def __init__(self, server_url: str = "http://localhost:8080"):
|
||||
self.server_url = server_url
|
||||
self.request_id = 0
|
||||
|
||||
def _call_method(self, method: str, params: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""调用JSON-RPC方法"""
|
||||
self.request_id += 1
|
||||
|
||||
payload = {
|
||||
"jsonrpc": "2.0",
|
||||
"method": method,
|
||||
"params": params,
|
||||
"id": self.request_id
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
self.server_url,
|
||||
json=payload,
|
||||
headers={'Content-Type': 'application/json'},
|
||||
timeout=60
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
return {
|
||||
"error": {
|
||||
"code": response.status_code,
|
||||
"message": f"HTTP Error: {response.text}"
|
||||
}
|
||||
}
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
return {
|
||||
"error": {
|
||||
"code": -1,
|
||||
"message": f"Request failed: {str(e)}"
|
||||
}
|
||||
}
|
||||
|
||||
def detect_scenes(self, video_path: str, detector_type: str = "content",
|
||||
threshold: float = 30.0, min_scene_length: float = 1.0) -> Dict[str, Any]:
|
||||
"""基础场景检测"""
|
||||
params = {
|
||||
"video_path": video_path,
|
||||
"detector_type": detector_type,
|
||||
"threshold": threshold,
|
||||
"min_scene_length": min_scene_length
|
||||
}
|
||||
|
||||
return self._call_method("scene.detect", params)
|
||||
|
||||
def detect_scenes_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]:
|
||||
"""LangGraph工作流场景检测"""
|
||||
params = {
|
||||
"video_path": video_path,
|
||||
"detector_type": detector_type,
|
||||
"threshold": threshold,
|
||||
"min_scene_length": min_scene_length,
|
||||
"enable_ai_analysis": enable_ai_analysis
|
||||
}
|
||||
|
||||
if output_path:
|
||||
params["output_path"] = output_path
|
||||
params["output_format"] = output_format
|
||||
|
||||
return self._call_method("scene.detect_workflow", params)
|
||||
|
||||
def get_video_info(self, video_path: str) -> Dict[str, Any]:
|
||||
"""获取视频信息"""
|
||||
params = {"video_path": video_path}
|
||||
return self._call_method("scene.get_video_info", params)
|
||||
|
||||
def 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]:
|
||||
"""批量场景检测"""
|
||||
params = {
|
||||
"directory": directory,
|
||||
"detector_type": detector_type,
|
||||
"threshold": threshold,
|
||||
"min_scene_length": min_scene_length,
|
||||
"output_format": output_format
|
||||
}
|
||||
|
||||
if output_dir:
|
||||
params["output_dir"] = output_dir
|
||||
|
||||
return self._call_method("scene.batch_detect", params)
|
||||
|
||||
|
||||
def demo_basic_detection():
|
||||
"""演示基础场景检测"""
|
||||
print("🎯 基础场景检测演示")
|
||||
print("=" * 50)
|
||||
|
||||
client = SceneDetectionClient("http://localhost:8081")
|
||||
|
||||
# 测试视频路径
|
||||
video_path = "assets/1/1752032011698.mp4"
|
||||
|
||||
print(f"📹 检测视频: {video_path}")
|
||||
|
||||
# 调用基础检测
|
||||
start_time = time.time()
|
||||
result = client.detect_scenes(video_path, threshold=15.0)
|
||||
end_time = time.time()
|
||||
|
||||
if "error" in result:
|
||||
print(f"❌ 检测失败: {result['error']}")
|
||||
return
|
||||
|
||||
detection_result = result.get("result", {})
|
||||
|
||||
if detection_result.get("success"):
|
||||
print(f"✅ 检测成功!")
|
||||
print(f" 场景数量: {detection_result['total_scenes']}")
|
||||
print(f" 视频时长: {detection_result['total_duration']:.2f}秒")
|
||||
print(f" 检测时间: {detection_result['detection_time']:.2f}秒")
|
||||
print(f" API调用时间: {end_time - start_time:.2f}秒")
|
||||
|
||||
# 显示场景详情
|
||||
scenes = detection_result.get("scenes", [])
|
||||
print(f"\n🎬 场景详情:")
|
||||
for scene in scenes[:5]: # 只显示前5个
|
||||
print(f" 场景 {scene['index']}: {scene['start_time']:.2f}s - {scene['end_time']:.2f}s")
|
||||
|
||||
if len(scenes) > 5:
|
||||
print(f" ... 还有 {len(scenes) - 5} 个场景")
|
||||
else:
|
||||
print(f"❌ 检测失败: {detection_result.get('error', '未知错误')}")
|
||||
|
||||
|
||||
def demo_workflow_detection():
|
||||
"""演示工作流场景检测"""
|
||||
print("\n🔄 LangGraph工作流检测演示")
|
||||
print("=" * 50)
|
||||
|
||||
client = SceneDetectionClient("http://localhost:8081")
|
||||
|
||||
# 测试视频路径
|
||||
video_path = "assets/1/1752032011698.mp4"
|
||||
|
||||
print(f"📹 检测视频: {video_path}")
|
||||
|
||||
# 调用工作流检测
|
||||
start_time = time.time()
|
||||
result = client.detect_scenes_workflow(
|
||||
video_path,
|
||||
threshold=15.0,
|
||||
enable_ai_analysis=False # 禁用AI分析以避免API密钥问题
|
||||
)
|
||||
end_time = time.time()
|
||||
|
||||
if "error" in result:
|
||||
print(f"❌ 工作流检测失败: {result['error']}")
|
||||
return
|
||||
|
||||
workflow_result = result.get("result", {})
|
||||
detection_result = workflow_result.get("detection_result", {})
|
||||
video_info = workflow_result.get("video_info", {})
|
||||
ai_analysis = workflow_result.get("ai_analysis")
|
||||
workflow_state = workflow_result.get("workflow_state")
|
||||
|
||||
if detection_result.get("success"):
|
||||
print(f"✅ 工作流检测成功!")
|
||||
print(f" 工作流状态: {workflow_state}")
|
||||
print(f" 场景数量: {detection_result['total_scenes']}")
|
||||
print(f" 检测时间: {detection_result['detection_time']:.2f}秒")
|
||||
print(f" API调用时间: {end_time - start_time:.2f}秒")
|
||||
|
||||
# 显示视频信息
|
||||
if video_info:
|
||||
print(f"\n📹 视频信息:")
|
||||
print(f" 分辨率: {video_info.get('resolution')}")
|
||||
print(f" 帧率: {video_info.get('fps'):.2f} fps")
|
||||
print(f" 时长: {video_info.get('duration'):.2f}秒")
|
||||
|
||||
# 显示AI分析结果
|
||||
if ai_analysis:
|
||||
print(f"\n🧠 AI分析: {ai_analysis}")
|
||||
else:
|
||||
print(f"❌ 工作流检测失败: {detection_result.get('error', '未知错误')}")
|
||||
|
||||
|
||||
def demo_video_info():
|
||||
"""演示获取视频信息"""
|
||||
print("\n📊 视频信息获取演示")
|
||||
print("=" * 50)
|
||||
|
||||
client = SceneDetectionClient("http://localhost:8081")
|
||||
|
||||
# 测试视频路径
|
||||
video_path = "assets/1/1752032011698.mp4"
|
||||
|
||||
print(f"📹 获取视频信息: {video_path}")
|
||||
|
||||
# 获取视频信息
|
||||
start_time = time.time()
|
||||
result = client.get_video_info(video_path)
|
||||
end_time = time.time()
|
||||
|
||||
if "error" in result:
|
||||
print(f"❌ 获取失败: {result['error']}")
|
||||
return
|
||||
|
||||
info_result = result.get("result", {})
|
||||
|
||||
if info_result.get("success"):
|
||||
info = info_result.get("info", {})
|
||||
print(f"✅ 获取成功!")
|
||||
print(f" 文件名: {info.get('filename')}")
|
||||
print(f" 分辨率: {info.get('resolution')}")
|
||||
print(f" 帧率: {info.get('fps'):.2f} fps")
|
||||
print(f" 总帧数: {info.get('frame_count'):,}")
|
||||
print(f" 时长: {info.get('duration'):.2f}秒")
|
||||
print(f" 文件大小: {info.get('file_size'):,} 字节")
|
||||
print(f" API调用时间: {end_time - start_time:.2f}秒")
|
||||
else:
|
||||
print(f"❌ 获取失败: {info_result.get('error', '未知错误')}")
|
||||
|
||||
|
||||
def main():
|
||||
"""主演示函数"""
|
||||
print("🚀 JSON-RPC 场景检测客户端演示")
|
||||
print("=" * 60)
|
||||
|
||||
# 检查服务器连接
|
||||
client = SceneDetectionClient("http://localhost:8081")
|
||||
|
||||
try:
|
||||
# 简单的连接测试
|
||||
test_result = client.get_video_info("nonexistent.mp4")
|
||||
if "error" in test_result and "Request failed" in str(test_result["error"]):
|
||||
print("❌ 无法连接到JSON-RPC服务器")
|
||||
print("💡 请先启动服务器: python3 -m python_core.cli jsonrpc start --port 8081")
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"❌ 连接测试失败: {e}")
|
||||
return
|
||||
|
||||
print("✅ 服务器连接正常")
|
||||
|
||||
# 运行演示
|
||||
demo_video_info()
|
||||
demo_basic_detection()
|
||||
demo_workflow_detection()
|
||||
|
||||
print("\n🎉 演示完成!")
|
||||
print("\n💡 更多用法:")
|
||||
print(" • 调整检测阈值以获得不同的场景分割效果")
|
||||
print(" • 使用不同的检测器类型 (content/threshold/adaptive)")
|
||||
print(" • 启用AI分析获得智能建议 (需要配置API密钥)")
|
||||
print(" • 批量处理多个视频文件")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,86 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test JSON-RPC Registration
|
||||
测试JSON-RPC方法注册
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
from python_core.utils.jsonrpc_enhanced import method_registry
|
||||
from python_core.scene_detection import SceneDetector
|
||||
|
||||
|
||||
def test_method_registration():
|
||||
"""测试方法注册"""
|
||||
print("🧪 测试JSON-RPC方法注册...")
|
||||
|
||||
# 创建检测器并注册方法
|
||||
detector = SceneDetector()
|
||||
detector.register_jsonrpc_methods()
|
||||
|
||||
# 检查注册的方法
|
||||
print(f"📋 已注册的方法: {list(method_registry.methods.keys())}")
|
||||
|
||||
# 测试方法调用
|
||||
test_request = '''
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "scene.get_video_info",
|
||||
"params": {
|
||||
"video_path": "assets/1/1752032011698.mp4"
|
||||
},
|
||||
"id": 1
|
||||
}
|
||||
'''
|
||||
|
||||
print("📤 测试方法调用...")
|
||||
response = method_registry.handle_request(test_request)
|
||||
|
||||
if response:
|
||||
print("📥 收到响应:")
|
||||
print(response)
|
||||
else:
|
||||
print("📥 收到空响应(异步模式)")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def test_direct_method_call():
|
||||
"""测试直接方法调用"""
|
||||
print("\n🧪 测试直接方法调用...")
|
||||
|
||||
detector = SceneDetector()
|
||||
|
||||
# 直接调用方法
|
||||
result = detector.jsonrpc_get_video_info("assets/1/1752032011698.mp4")
|
||||
|
||||
print(f"📋 直接调用结果: {result}")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("🚀 开始测试JSON-RPC方法注册")
|
||||
print("=" * 50)
|
||||
|
||||
try:
|
||||
test_method_registration()
|
||||
test_direct_method_call()
|
||||
print("\n✅ 所有测试通过")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"\n❌ 测试失败: {e}")
|
||||
import traceback
|
||||
print(f"详细错误: {traceback.format_exc()}")
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = main()
|
||||
sys.exit(0 if success else 1)
|
||||
@@ -1,271 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
测试JSON-RPC工作流进度反馈
|
||||
Test JSON-RPC Workflow Progress Feedback
|
||||
"""
|
||||
|
||||
import json
|
||||
import requests
|
||||
import time
|
||||
import threading
|
||||
import websocket
|
||||
from typing import Dict, Any
|
||||
|
||||
|
||||
class JSONRPCWorkflowTester:
|
||||
"""JSON-RPC工作流测试器"""
|
||||
|
||||
def __init__(self, server_url: str = "http://localhost:8081"):
|
||||
self.server_url = server_url
|
||||
self.request_id = 0
|
||||
self.progress_updates = []
|
||||
|
||||
def _call_method(self, method: str, params: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""调用JSON-RPC方法"""
|
||||
self.request_id += 1
|
||||
|
||||
payload = {
|
||||
"jsonrpc": "2.0",
|
||||
"method": method,
|
||||
"params": params,
|
||||
"id": self.request_id
|
||||
}
|
||||
|
||||
print(f"📤 发送请求 (ID: {self.request_id}): {method}")
|
||||
print(f" 参数: {json.dumps(params, indent=2, ensure_ascii=False)}")
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
self.server_url,
|
||||
json=payload,
|
||||
headers={'Content-Type': 'application/json'},
|
||||
timeout=120 # 增加超时时间
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
print(f"📥 收到响应 (ID: {self.request_id}): {response.status_code}")
|
||||
return result
|
||||
else:
|
||||
print(f"❌ HTTP错误: {response.status_code}")
|
||||
return {
|
||||
"error": {
|
||||
"code": response.status_code,
|
||||
"message": f"HTTP Error: {response.text}"
|
||||
}
|
||||
}
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"❌ 请求失败: {str(e)}")
|
||||
return {
|
||||
"error": {
|
||||
"code": -1,
|
||||
"message": f"Request failed: {str(e)}"
|
||||
}
|
||||
}
|
||||
|
||||
def test_basic_detection(self):
|
||||
"""测试基础场景检测"""
|
||||
print("\n" + "="*60)
|
||||
print("🎯 测试基础场景检测")
|
||||
print("="*60)
|
||||
|
||||
params = {
|
||||
"video_path": "assets/1/1752032011698.mp4",
|
||||
"detector_type": "content",
|
||||
"threshold": 15.0,
|
||||
"min_scene_length": 1.0
|
||||
}
|
||||
|
||||
start_time = time.time()
|
||||
result = self._call_method("scene.detect", params)
|
||||
end_time = time.time()
|
||||
|
||||
if "error" in result:
|
||||
print(f"❌ 检测失败: {result['error']}")
|
||||
return False
|
||||
|
||||
detection_result = result.get("result", {})
|
||||
if detection_result.get("success"):
|
||||
print(f"✅ 基础检测成功!")
|
||||
print(f" 场景数量: {detection_result['total_scenes']}")
|
||||
print(f" 检测时间: {detection_result['detection_time']:.2f}秒")
|
||||
print(f" API调用时间: {end_time - start_time:.2f}秒")
|
||||
return True
|
||||
else:
|
||||
print(f"❌ 检测失败: {detection_result.get('error', '未知错误')}")
|
||||
return False
|
||||
|
||||
def test_workflow_detection(self):
|
||||
"""测试工作流场景检测"""
|
||||
print("\n" + "="*60)
|
||||
print("🔄 测试LangGraph工作流检测")
|
||||
print("="*60)
|
||||
|
||||
params = {
|
||||
"video_path": "assets/1/1752032011698.mp4",
|
||||
"detector_type": "content",
|
||||
"threshold": 15.0,
|
||||
"min_scene_length": 1.0,
|
||||
"enable_ai_analysis": False # 禁用AI分析避免API密钥问题
|
||||
}
|
||||
|
||||
start_time = time.time()
|
||||
result = self._call_method("scene.detect_workflow", params)
|
||||
end_time = time.time()
|
||||
|
||||
if "error" in result:
|
||||
print(f"❌ 工作流检测失败: {result['error']}")
|
||||
return False
|
||||
|
||||
workflow_result = result.get("result", {})
|
||||
detection_result = workflow_result.get("detection_result", {})
|
||||
|
||||
if detection_result.get("success"):
|
||||
print(f"✅ 工作流检测成功!")
|
||||
print(f" 工作流状态: {workflow_result.get('workflow_state')}")
|
||||
print(f" 场景数量: {detection_result['total_scenes']}")
|
||||
print(f" 检测时间: {detection_result['detection_time']:.2f}秒")
|
||||
print(f" API调用时间: {end_time - start_time:.2f}秒")
|
||||
|
||||
# 显示视频信息
|
||||
video_info = workflow_result.get("video_info", {})
|
||||
if video_info:
|
||||
print(f" 视频信息: {video_info.get('resolution')}, {video_info.get('fps'):.2f}fps")
|
||||
|
||||
# 显示AI分析结果
|
||||
ai_analysis = workflow_result.get("ai_analysis")
|
||||
if ai_analysis:
|
||||
print(f" AI分析: {ai_analysis[:100]}...")
|
||||
|
||||
return True
|
||||
else:
|
||||
print(f"❌ 工作流检测失败: {detection_result.get('error', '未知错误')}")
|
||||
return False
|
||||
|
||||
def test_video_info(self):
|
||||
"""测试视频信息获取"""
|
||||
print("\n" + "="*60)
|
||||
print("📊 测试视频信息获取")
|
||||
print("="*60)
|
||||
|
||||
params = {
|
||||
"video_path": "assets/1/1752032011698.mp4"
|
||||
}
|
||||
|
||||
start_time = time.time()
|
||||
result = self._call_method("scene.get_video_info", params)
|
||||
end_time = time.time()
|
||||
|
||||
if "error" in result:
|
||||
print(f"❌ 获取失败: {result['error']}")
|
||||
return False
|
||||
|
||||
info_result = result.get("result", {})
|
||||
if info_result.get("success"):
|
||||
info = info_result.get("info", {})
|
||||
print(f"✅ 信息获取成功!")
|
||||
print(f" 文件名: {info.get('filename')}")
|
||||
print(f" 分辨率: {info.get('resolution')}")
|
||||
print(f" 帧率: {info.get('fps'):.2f} fps")
|
||||
print(f" 时长: {info.get('duration'):.2f}秒")
|
||||
print(f" 文件大小: {info.get('file_size'):,} 字节")
|
||||
print(f" API调用时间: {end_time - start_time:.2f}秒")
|
||||
return True
|
||||
else:
|
||||
print(f"❌ 获取失败: {info_result.get('error', '未知错误')}")
|
||||
return False
|
||||
|
||||
def test_error_handling(self):
|
||||
"""测试错误处理"""
|
||||
print("\n" + "="*60)
|
||||
print("🚨 测试错误处理")
|
||||
print("="*60)
|
||||
|
||||
# 测试不存在的文件
|
||||
params = {
|
||||
"video_path": "nonexistent_video.mp4",
|
||||
"threshold": 15.0
|
||||
}
|
||||
|
||||
result = self._call_method("scene.detect", params)
|
||||
|
||||
if "error" in result:
|
||||
print(f"✅ 正确处理了请求错误: {result['error']['message']}")
|
||||
else:
|
||||
detection_result = result.get("result", {})
|
||||
if not detection_result.get("success"):
|
||||
print(f"✅ 正确处理了应用错误: {detection_result.get('error')}")
|
||||
else:
|
||||
print(f"❌ 应该返回错误,但返回了成功结果")
|
||||
return False
|
||||
|
||||
# 测试无效的方法
|
||||
result = self._call_method("scene.invalid_method", {})
|
||||
if "error" in result and result["error"]["code"] == -32601:
|
||||
print(f"✅ 正确处理了方法不存在错误")
|
||||
else:
|
||||
print(f"❌ 没有正确处理方法不存在错误")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def run_all_tests(self):
|
||||
"""运行所有测试"""
|
||||
print("🚀 JSON-RPC工作流测试开始")
|
||||
print("="*60)
|
||||
|
||||
# 检查服务器连接
|
||||
try:
|
||||
test_result = self._call_method("scene.get_video_info", {"video_path": "test.mp4"})
|
||||
if "error" in test_result and "Request failed" in str(test_result["error"]):
|
||||
print("❌ 无法连接到JSON-RPC服务器")
|
||||
print("💡 请先启动服务器: python3 -m python_core.cli jsonrpc start --port 8081")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"❌ 连接测试失败: {e}")
|
||||
return False
|
||||
|
||||
print("✅ 服务器连接正常")
|
||||
|
||||
# 运行测试
|
||||
tests = [
|
||||
("视频信息获取", self.test_video_info),
|
||||
("基础场景检测", self.test_basic_detection),
|
||||
("工作流场景检测", self.test_workflow_detection),
|
||||
("错误处理", self.test_error_handling),
|
||||
]
|
||||
|
||||
passed = 0
|
||||
total = len(tests)
|
||||
|
||||
for test_name, test_func in tests:
|
||||
try:
|
||||
if test_func():
|
||||
passed += 1
|
||||
print(f"✅ {test_name} - 通过")
|
||||
else:
|
||||
print(f"❌ {test_name} - 失败")
|
||||
except Exception as e:
|
||||
print(f"❌ {test_name} - 异常: {e}")
|
||||
|
||||
print("\n" + "="*60)
|
||||
print(f"🎉 测试完成: {passed}/{total} 通过")
|
||||
print("="*60)
|
||||
|
||||
if passed == total:
|
||||
print("🎊 所有测试都通过了!JSON-RPC工作流功能正常")
|
||||
else:
|
||||
print("⚠️ 部分测试失败,请检查服务器和配置")
|
||||
|
||||
return passed == total
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
tester = JSONRPCWorkflowTester("http://localhost:8081")
|
||||
tester.run_all_tests()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,248 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test Refactored Modules
|
||||
测试重构后的模块
|
||||
|
||||
验证重构后的场景检测模块是否正常工作
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
from python_core.scene_detection import (
|
||||
SceneDetector,
|
||||
DetectorType,
|
||||
OutputFormat,
|
||||
SceneInfo,
|
||||
DetectionResult,
|
||||
SceneDetectionWorkflowState
|
||||
)
|
||||
|
||||
|
||||
def test_imports():
|
||||
"""测试模块导入"""
|
||||
print("🧪 测试模块导入...")
|
||||
|
||||
try:
|
||||
# 测试类型导入
|
||||
assert DetectorType.CONTENT == "content"
|
||||
assert OutputFormat.JSON == "json"
|
||||
print("✅ 类型导入成功")
|
||||
|
||||
# 测试数据模型
|
||||
scene = SceneInfo(0, 0.0, 5.0, 5.0)
|
||||
assert scene.index == 0
|
||||
assert scene.duration == 5.0
|
||||
print("✅ 数据模型导入成功")
|
||||
|
||||
# 测试主接口类
|
||||
detector = SceneDetector()
|
||||
assert detector is not None
|
||||
assert hasattr(detector, 'detect_scenes')
|
||||
assert hasattr(detector, 'detect_with_workflow')
|
||||
print("✅ 主接口类导入成功")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 导入测试失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_basic_functionality():
|
||||
"""测试基础功能"""
|
||||
print("\n🧪 测试基础功能...")
|
||||
|
||||
try:
|
||||
detector = SceneDetector()
|
||||
|
||||
# 测试视频信息获取
|
||||
video_path = Path("assets/1/1752032011698.mp4")
|
||||
if video_path.exists():
|
||||
info_result = detector.get_video_info(video_path)
|
||||
if info_result["success"]:
|
||||
print("✅ 视频信息获取成功")
|
||||
print(f" 分辨率: {info_result['info']['resolution']}")
|
||||
print(f" 时长: {info_result['info']['duration']:.2f}秒")
|
||||
else:
|
||||
print(f"⚠️ 视频信息获取失败: {info_result['error']}")
|
||||
else:
|
||||
print("⚠️ 测试视频文件不存在,跳过视频信息测试")
|
||||
|
||||
# 测试JSON-RPC方法注册
|
||||
detector.register_jsonrpc_methods()
|
||||
print("✅ JSON-RPC方法注册成功")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 基础功能测试失败: {e}")
|
||||
import traceback
|
||||
print(f"详细错误: {traceback.format_exc()}")
|
||||
return False
|
||||
|
||||
|
||||
def test_services():
|
||||
"""测试服务层"""
|
||||
print("\n🧪 测试服务层...")
|
||||
|
||||
try:
|
||||
from python_core.scene_detection.services import (
|
||||
SceneDetectorService,
|
||||
VideoInfoService,
|
||||
AIAnalysisService
|
||||
)
|
||||
|
||||
# 测试检测服务
|
||||
detector_service = SceneDetectorService()
|
||||
assert len(detector_service.supported_formats) > 0
|
||||
print("✅ 场景检测服务初始化成功")
|
||||
|
||||
# 测试视频信息服务
|
||||
video_info_service = VideoInfoService()
|
||||
assert hasattr(video_info_service, 'extract_video_info')
|
||||
print("✅ 视频信息服务初始化成功")
|
||||
|
||||
# 测试AI分析服务
|
||||
ai_service = AIAnalysisService()
|
||||
assert hasattr(ai_service, 'analyze_detection_result')
|
||||
print(f"✅ AI分析服务初始化成功 (AI启用: {ai_service.ai_enabled})")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 服务层测试失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_workflows():
|
||||
"""测试工作流"""
|
||||
print("\n🧪 测试工作流...")
|
||||
|
||||
try:
|
||||
from python_core.scene_detection.workflows import (
|
||||
SceneDetectionWorkflowManager,
|
||||
WorkflowNodes
|
||||
)
|
||||
|
||||
# 测试工作流管理器
|
||||
workflow_manager = SceneDetectionWorkflowManager()
|
||||
assert hasattr(workflow_manager, 'create_detection_workflow')
|
||||
print("✅ 工作流管理器初始化成功")
|
||||
|
||||
# 测试工作流节点
|
||||
nodes = WorkflowNodes()
|
||||
assert hasattr(nodes, 'validate_input')
|
||||
assert hasattr(nodes, 'detect_scenes')
|
||||
assert hasattr(nodes, 'finalize_results')
|
||||
print("✅ 工作流节点初始化成功")
|
||||
|
||||
# 测试工作流创建
|
||||
workflow = workflow_manager.create_detection_workflow()
|
||||
if workflow:
|
||||
print("✅ LangGraph工作流创建成功")
|
||||
else:
|
||||
print("⚠️ LangGraph工作流创建失败(可能是依赖问题)")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 工作流测试失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_utils():
|
||||
"""测试工具类"""
|
||||
print("\n🧪 测试工具类...")
|
||||
|
||||
try:
|
||||
from python_core.scene_detection.utils import (
|
||||
ResultSaver,
|
||||
InputValidator
|
||||
)
|
||||
|
||||
# 测试结果保存器
|
||||
saver = ResultSaver()
|
||||
assert hasattr(saver, 'save_results')
|
||||
print("✅ 结果保存器初始化成功")
|
||||
|
||||
# 测试输入验证器
|
||||
validator = InputValidator({'.mp4', '.avi'})
|
||||
assert hasattr(validator, 'validate_video_path')
|
||||
print("✅ 输入验证器初始化成功")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 工具类测试失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_cli_integration():
|
||||
"""测试CLI集成"""
|
||||
print("\n🧪 测试CLI集成...")
|
||||
|
||||
try:
|
||||
# 测试新的CLI模块
|
||||
from python_core.cli.scene_detect_new import app
|
||||
assert app is not None
|
||||
print("✅ 新CLI模块导入成功")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ CLI集成测试失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
"""主测试函数"""
|
||||
print("🚀 开始测试重构后的场景检测模块")
|
||||
print("=" * 60)
|
||||
|
||||
tests = [
|
||||
("模块导入", test_imports),
|
||||
("基础功能", test_basic_functionality),
|
||||
("服务层", test_services),
|
||||
("工作流", test_workflows),
|
||||
("工具类", test_utils),
|
||||
("CLI集成", test_cli_integration),
|
||||
]
|
||||
|
||||
passed = 0
|
||||
total = len(tests)
|
||||
|
||||
for test_name, test_func in tests:
|
||||
try:
|
||||
if test_func():
|
||||
passed += 1
|
||||
print(f"✅ {test_name} - 通过")
|
||||
else:
|
||||
print(f"❌ {test_name} - 失败")
|
||||
except Exception as e:
|
||||
print(f"❌ {test_name} - 异常: {e}")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print(f"🎉 测试完成: {passed}/{total} 通过")
|
||||
|
||||
if passed == total:
|
||||
print("🎊 所有测试都通过了!重构成功!")
|
||||
print("\n📋 重构后的模块结构:")
|
||||
print("├── types/ # 类型定义和数据模型")
|
||||
print("├── services/ # 核心业务逻辑服务")
|
||||
print("├── workflows/ # LangGraph工作流")
|
||||
print("├── utils/ # 工具类和辅助函数")
|
||||
print("└── scene_detector.py # 主接口类")
|
||||
else:
|
||||
print("⚠️ 部分测试失败,请检查重构实现")
|
||||
|
||||
return passed == total
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = main()
|
||||
sys.exit(0 if success else 1)
|
||||
@@ -1,206 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test Scene Detection Fix
|
||||
测试场景检测修复
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
from python_core.scene_detection.services import SceneDetectorService
|
||||
from python_core.scene_detection.types import DetectorType
|
||||
|
||||
|
||||
def test_basic_detection():
|
||||
"""测试基础场景检测"""
|
||||
print("🧪 测试基础场景检测...")
|
||||
|
||||
try:
|
||||
# 创建检测服务
|
||||
detector_service = SceneDetectorService()
|
||||
|
||||
# 测试视频路径
|
||||
video_path = Path("assets/1/1752032011698.mp4")
|
||||
|
||||
if not video_path.exists():
|
||||
print(f"❌ 测试视频不存在: {video_path}")
|
||||
return False
|
||||
|
||||
print(f"📹 测试视频: {video_path}")
|
||||
|
||||
# 执行检测
|
||||
result = detector_service.detect_scenes(
|
||||
video_path,
|
||||
DetectorType.CONTENT,
|
||||
30.0,
|
||||
1.0
|
||||
)
|
||||
|
||||
if result.success:
|
||||
print(f"✅ 检测成功!")
|
||||
print(f" 场景数量: {result.total_scenes}")
|
||||
print(f" 检测时间: {result.detection_time:.2f}秒")
|
||||
print(f" 视频时长: {result.total_duration:.2f}秒")
|
||||
|
||||
# 显示前几个场景
|
||||
for i, scene in enumerate(result.scenes[:3]):
|
||||
print(f" 场景 {i+1}: {scene.start_time:.2f}s - {scene.end_time:.2f}s (时长: {scene.duration:.2f}s)")
|
||||
|
||||
if len(result.scenes) > 3:
|
||||
print(f" ... 还有 {len(result.scenes) - 3} 个场景")
|
||||
|
||||
return True
|
||||
else:
|
||||
print(f"❌ 检测失败: {result.error}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 测试异常: {e}")
|
||||
import traceback
|
||||
print(f"详细错误: {traceback.format_exc()}")
|
||||
return False
|
||||
|
||||
|
||||
def test_video_info():
|
||||
"""测试视频信息提取"""
|
||||
print("\n🧪 测试视频信息提取...")
|
||||
|
||||
try:
|
||||
from python_core.scene_detection.services import VideoInfoService
|
||||
|
||||
video_info_service = VideoInfoService()
|
||||
video_path = Path("assets/1/1752032011698.mp4")
|
||||
|
||||
if not video_path.exists():
|
||||
print(f"❌ 测试视频不存在: {video_path}")
|
||||
return False
|
||||
|
||||
info = video_info_service.extract_video_info(video_path)
|
||||
|
||||
print(f"✅ 视频信息提取成功!")
|
||||
print(f" 文件名: {info['filename']}")
|
||||
print(f" 分辨率: {info['resolution']}")
|
||||
print(f" 帧率: {info['fps']:.2f} fps")
|
||||
print(f" 时长: {info['duration']:.2f}秒")
|
||||
print(f" 文件大小: {info['file_size']:,} 字节")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 测试异常: {e}")
|
||||
import traceback
|
||||
print(f"详细错误: {traceback.format_exc()}")
|
||||
return False
|
||||
|
||||
|
||||
def test_pyscenedetect_version():
|
||||
"""测试PySceneDetect版本和API"""
|
||||
print("\n🧪 测试PySceneDetect版本和API...")
|
||||
|
||||
try:
|
||||
import scenedetect
|
||||
print(f"📦 PySceneDetect版本: {scenedetect.__version__}")
|
||||
|
||||
# 测试基本API
|
||||
from scenedetect import open_video, SceneManager
|
||||
from scenedetect.detectors import ContentDetector
|
||||
|
||||
video_path = Path("assets/1/1752032011698.mp4")
|
||||
if not video_path.exists():
|
||||
print(f"❌ 测试视频不存在: {video_path}")
|
||||
return False
|
||||
|
||||
# 打开视频
|
||||
video = open_video(str(video_path))
|
||||
scene_manager = SceneManager()
|
||||
scene_manager.add_detector(ContentDetector(threshold=30.0))
|
||||
|
||||
print("📊 执行基础场景检测...")
|
||||
scene_manager.detect_scenes(video, show_progress=False)
|
||||
scene_list = scene_manager.get_scene_list()
|
||||
|
||||
print(f"✅ 基础检测成功,找到 {len(scene_list)} 个场景")
|
||||
|
||||
# 测试时间码API
|
||||
if scene_list:
|
||||
start_time, end_time = scene_list[0]
|
||||
print(f"📋 第一个场景时间码类型: {type(start_time)}")
|
||||
|
||||
# 测试不同的时间获取方法
|
||||
methods = []
|
||||
|
||||
if hasattr(start_time, 'total_seconds'):
|
||||
try:
|
||||
seconds = start_time.total_seconds()
|
||||
methods.append(f"total_seconds(): {seconds:.2f}")
|
||||
except Exception as e:
|
||||
methods.append(f"total_seconds() 失败: {e}")
|
||||
|
||||
if hasattr(start_time, 'get_seconds'):
|
||||
try:
|
||||
seconds = start_time.get_seconds()
|
||||
methods.append(f"get_seconds(): {seconds:.2f}")
|
||||
except Exception as e:
|
||||
methods.append(f"get_seconds() 失败: {e}")
|
||||
|
||||
try:
|
||||
seconds = float(start_time)
|
||||
methods.append(f"float(): {seconds:.2f}")
|
||||
except Exception as e:
|
||||
methods.append(f"float() 失败: {e}")
|
||||
|
||||
print("🔍 可用的时间获取方法:")
|
||||
for method in methods:
|
||||
print(f" - {method}")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 测试异常: {e}")
|
||||
import traceback
|
||||
print(f"详细错误: {traceback.format_exc()}")
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("🚀 开始测试场景检测修复")
|
||||
print("=" * 60)
|
||||
|
||||
tests = [
|
||||
("PySceneDetect版本和API", test_pyscenedetect_version),
|
||||
("视频信息提取", test_video_info),
|
||||
("基础场景检测", test_basic_detection),
|
||||
]
|
||||
|
||||
passed = 0
|
||||
total = len(tests)
|
||||
|
||||
for test_name, test_func in tests:
|
||||
try:
|
||||
if test_func():
|
||||
passed += 1
|
||||
print(f"✅ {test_name} - 通过")
|
||||
else:
|
||||
print(f"❌ {test_name} - 失败")
|
||||
except Exception as e:
|
||||
print(f"❌ {test_name} - 异常: {e}")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print(f"🎉 测试完成: {passed}/{total} 通过")
|
||||
|
||||
if passed == total:
|
||||
print("🎊 所有测试都通过了!场景检测修复成功!")
|
||||
else:
|
||||
print("⚠️ 部分测试失败,需要进一步调试")
|
||||
|
||||
return passed == total
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = main()
|
||||
sys.exit(0 if success else 1)
|
||||
@@ -1,303 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
测试工作流结果返回
|
||||
Test Workflow Result Return
|
||||
"""
|
||||
|
||||
import json
|
||||
import requests
|
||||
import time
|
||||
import threading
|
||||
import queue
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
|
||||
class WorkflowResultTester:
|
||||
"""工作流结果测试器"""
|
||||
|
||||
def __init__(self, server_url: str = "http://localhost:8081"):
|
||||
self.server_url = server_url
|
||||
self.request_id = 0
|
||||
self.progress_queue = queue.Queue()
|
||||
self.result_queue = queue.Queue()
|
||||
|
||||
def _call_method_async(self, method: str, params: Dict[str, Any]) -> str:
|
||||
"""异步调用JSON-RPC方法"""
|
||||
self.request_id += 1
|
||||
request_id = f"test_{self.request_id}_{int(time.time())}"
|
||||
|
||||
payload = {
|
||||
"jsonrpc": "2.0",
|
||||
"method": method,
|
||||
"params": params,
|
||||
"id": request_id
|
||||
}
|
||||
|
||||
print(f"📤 发送异步请求 (ID: {request_id}): {method}")
|
||||
print(f" 参数: {json.dumps(params, indent=2, ensure_ascii=False)}")
|
||||
|
||||
def make_request():
|
||||
try:
|
||||
response = requests.post(
|
||||
self.server_url,
|
||||
json=payload,
|
||||
headers={'Content-Type': 'application/json'},
|
||||
timeout=120
|
||||
)
|
||||
|
||||
print(f"📥 收到HTTP响应 (ID: {request_id}): {response.status_code}")
|
||||
|
||||
if response.status_code == 200:
|
||||
# 检查是否有响应体
|
||||
if response.text.strip():
|
||||
try:
|
||||
result = response.json()
|
||||
print(f"📋 JSON响应内容: {json.dumps(result, indent=2, ensure_ascii=False)}")
|
||||
self.result_queue.put(("response", request_id, result))
|
||||
except json.JSONDecodeError:
|
||||
print(f"⚠️ 响应不是有效的JSON: {response.text}")
|
||||
self.result_queue.put(("invalid_json", request_id, response.text))
|
||||
else:
|
||||
print(f"✅ 空响应体 - 表示结果已异步发送")
|
||||
self.result_queue.put(("empty_response", request_id, None))
|
||||
else:
|
||||
print(f"❌ HTTP错误: {response.status_code} - {response.text}")
|
||||
self.result_queue.put(("http_error", request_id, {
|
||||
"status_code": response.status_code,
|
||||
"text": response.text
|
||||
}))
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"❌ 请求异常: {str(e)}")
|
||||
self.result_queue.put(("request_error", request_id, str(e)))
|
||||
|
||||
# 在后台线程中发送请求
|
||||
thread = threading.Thread(target=make_request)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
return request_id
|
||||
|
||||
def test_workflow_with_progress_monitoring(self):
|
||||
"""测试带进度监控的工作流"""
|
||||
print("\n" + "="*60)
|
||||
print("🔄 测试工作流进度监控和结果返回")
|
||||
print("="*60)
|
||||
|
||||
# 启动进度监控(模拟WebSocket或长轮询)
|
||||
progress_thread = threading.Thread(target=self._monitor_progress)
|
||||
progress_thread.daemon = True
|
||||
progress_thread.start()
|
||||
|
||||
# 发送工作流请求
|
||||
params = {
|
||||
"video_path": "assets/1/1752032011698.mp4",
|
||||
"detector_type": "content",
|
||||
"threshold": 15.0,
|
||||
"min_scene_length": 1.0,
|
||||
"enable_ai_analysis": False
|
||||
}
|
||||
|
||||
request_id = self._call_method_async("scene.detect_workflow", params)
|
||||
|
||||
# 等待结果
|
||||
print(f"⏳ 等待工作流完成 (Request ID: {request_id})...")
|
||||
|
||||
start_time = time.time()
|
||||
timeout = 60 # 60秒超时
|
||||
|
||||
while time.time() - start_time < timeout:
|
||||
try:
|
||||
result_type, result_id, result_data = self.result_queue.get(timeout=1)
|
||||
|
||||
if result_id == request_id:
|
||||
print(f"\n📋 收到结果 (类型: {result_type}):")
|
||||
|
||||
if result_type == "response":
|
||||
print("✅ 收到JSON-RPC响应:")
|
||||
print(json.dumps(result_data, indent=2, ensure_ascii=False))
|
||||
return True
|
||||
elif result_type == "empty_response":
|
||||
print("✅ 收到空响应 - 结果已通过进度通道发送")
|
||||
return True
|
||||
elif result_type == "http_error":
|
||||
print(f"❌ HTTP错误: {result_data}")
|
||||
return False
|
||||
elif result_type == "request_error":
|
||||
print(f"❌ 请求错误: {result_data}")
|
||||
return False
|
||||
else:
|
||||
print(f"⚠️ 未知结果类型: {result_type}")
|
||||
return False
|
||||
|
||||
except queue.Empty:
|
||||
continue
|
||||
|
||||
print(f"⏰ 等待超时 ({timeout}秒)")
|
||||
return False
|
||||
|
||||
def _monitor_progress(self):
|
||||
"""监控进度更新(模拟)"""
|
||||
# 这里应该是WebSocket连接或长轮询
|
||||
# 为了演示,我们只是打印一些模拟的进度信息
|
||||
print("📊 进度监控已启动...")
|
||||
|
||||
# 模拟进度更新
|
||||
progress_steps = [
|
||||
("validate", "🔍 验证输入参数..."),
|
||||
("extract_info", "📊 提取视频信息..."),
|
||||
("detect", "🎯 执行场景检测..."),
|
||||
("analyze", "🧠 AI分析场景结果..."),
|
||||
("finalize", "📋 整理最终结果...")
|
||||
]
|
||||
|
||||
for step, message in progress_steps:
|
||||
time.sleep(2) # 模拟处理时间
|
||||
print(f"📈 [进度] {step}: {message}")
|
||||
|
||||
def test_basic_method_comparison(self):
|
||||
"""测试基础方法对比"""
|
||||
print("\n" + "="*60)
|
||||
print("🔍 测试基础方法 vs 工作流方法")
|
||||
print("="*60)
|
||||
|
||||
# 测试基础方法(应该返回结果)
|
||||
print("1️⃣ 测试基础场景检测方法:")
|
||||
basic_params = {
|
||||
"video_path": "assets/1/1752032011698.mp4",
|
||||
"threshold": 15.0
|
||||
}
|
||||
|
||||
basic_request_id = self._call_method_async("scene.detect", basic_params)
|
||||
|
||||
# 等待基础方法结果
|
||||
time.sleep(5)
|
||||
|
||||
try:
|
||||
result_type, result_id, result_data = self.result_queue.get(timeout=1)
|
||||
if result_id == basic_request_id:
|
||||
if result_type == "response":
|
||||
print("✅ 基础方法正确返回了JSON响应")
|
||||
print(f" 场景数: {result_data.get('result', {}).get('total_scenes', 'N/A')}")
|
||||
else:
|
||||
print(f"⚠️ 基础方法返回了意外的结果类型: {result_type}")
|
||||
except queue.Empty:
|
||||
print("⏰ 基础方法响应超时")
|
||||
|
||||
print("\n2️⃣ 测试工作流方法:")
|
||||
# 工作流方法测试在上面的方法中进行
|
||||
return self.test_workflow_with_progress_monitoring()
|
||||
|
||||
def test_error_handling(self):
|
||||
"""测试错误处理"""
|
||||
print("\n" + "="*60)
|
||||
print("🚨 测试工作流错误处理")
|
||||
print("="*60)
|
||||
|
||||
# 测试不存在的文件
|
||||
params = {
|
||||
"video_path": "nonexistent_video.mp4",
|
||||
"threshold": 15.0,
|
||||
"enable_ai_analysis": False
|
||||
}
|
||||
|
||||
request_id = self._call_method_async("scene.detect_workflow", params)
|
||||
|
||||
# 等待错误结果
|
||||
print(f"⏳ 等待错误处理 (Request ID: {request_id})...")
|
||||
|
||||
start_time = time.time()
|
||||
timeout = 30
|
||||
|
||||
while time.time() - start_time < timeout:
|
||||
try:
|
||||
result_type, result_id, result_data = self.result_queue.get(timeout=1)
|
||||
|
||||
if result_id == request_id:
|
||||
if result_type == "response" and "error" in result_data:
|
||||
print("✅ 正确收到错误响应:")
|
||||
print(json.dumps(result_data, indent=2, ensure_ascii=False))
|
||||
return True
|
||||
elif result_type == "empty_response":
|
||||
print("✅ 错误已通过进度通道发送")
|
||||
return True
|
||||
else:
|
||||
print(f"⚠️ 意外的错误处理结果: {result_type}")
|
||||
return False
|
||||
|
||||
except queue.Empty:
|
||||
continue
|
||||
|
||||
print(f"⏰ 错误处理等待超时")
|
||||
return False
|
||||
|
||||
def run_all_tests(self):
|
||||
"""运行所有测试"""
|
||||
print("🚀 工作流结果返回测试开始")
|
||||
print("="*60)
|
||||
|
||||
# 检查服务器连接
|
||||
try:
|
||||
response = requests.get(f"{self.server_url.replace('//', '//').replace('http:', 'http:').replace('8081', '8081')}")
|
||||
except:
|
||||
try:
|
||||
# 简单的连接测试
|
||||
test_payload = {
|
||||
"jsonrpc": "2.0",
|
||||
"method": "scene.get_video_info",
|
||||
"params": {"video_path": "test.mp4"},
|
||||
"id": "test"
|
||||
}
|
||||
response = requests.post(
|
||||
self.server_url,
|
||||
json=test_payload,
|
||||
headers={'Content-Type': 'application/json'},
|
||||
timeout=5
|
||||
)
|
||||
except Exception as e:
|
||||
print("❌ 无法连接到JSON-RPC服务器")
|
||||
print("💡 请先启动服务器: python3 -m python_core.cli jsonrpc start --port 8081")
|
||||
return False
|
||||
|
||||
print("✅ 服务器连接正常")
|
||||
|
||||
# 运行测试
|
||||
tests = [
|
||||
("基础方法对比", self.test_basic_method_comparison),
|
||||
("错误处理", self.test_error_handling),
|
||||
]
|
||||
|
||||
passed = 0
|
||||
total = len(tests)
|
||||
|
||||
for test_name, test_func in tests:
|
||||
try:
|
||||
print(f"\n🧪 运行测试: {test_name}")
|
||||
if test_func():
|
||||
passed += 1
|
||||
print(f"✅ {test_name} - 通过")
|
||||
else:
|
||||
print(f"❌ {test_name} - 失败")
|
||||
except Exception as e:
|
||||
print(f"❌ {test_name} - 异常: {e}")
|
||||
|
||||
print("\n" + "="*60)
|
||||
print(f"🎉 测试完成: {passed}/{total} 通过")
|
||||
print("="*60)
|
||||
|
||||
if passed == total:
|
||||
print("🎊 所有测试都通过了!工作流结果返回功能正常")
|
||||
else:
|
||||
print("⚠️ 部分测试失败,请检查实现")
|
||||
|
||||
return passed == total
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
tester = WorkflowResultTester("http://localhost:8081")
|
||||
tester.run_all_tests()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,416 +0,0 @@
|
||||
/**
|
||||
* Text Video Agent API 使用示例
|
||||
*/
|
||||
|
||||
import {
|
||||
TextVideoAgentAPI,
|
||||
textVideoAgentAPI,
|
||||
ImageGenerationParams,
|
||||
VideoGenerationParams,
|
||||
TaskRequest
|
||||
} from '../src/services/textVideoAgentAPI'
|
||||
|
||||
import {
|
||||
TaskType,
|
||||
AspectRatio,
|
||||
VideoDuration,
|
||||
PRESET_CONFIGS
|
||||
} from '../src/services/textVideoAgentTypes'
|
||||
|
||||
// ==================== 基础使用示例 ====================
|
||||
|
||||
/**
|
||||
* 示例1: 基础健康检查
|
||||
*/
|
||||
async function example1_HealthCheck() {
|
||||
console.log('=== 健康检查示例 ===')
|
||||
|
||||
try {
|
||||
const health = await textVideoAgentAPI.healthCheck()
|
||||
console.log('服务状态:', health)
|
||||
|
||||
const mjHealth = await textVideoAgentAPI.mjHealthCheck()
|
||||
console.log('Midjourney状态:', mjHealth)
|
||||
|
||||
const jmHealth = await textVideoAgentAPI.jmHealthCheck()
|
||||
console.log('极梦状态:', jmHealth)
|
||||
} catch (error) {
|
||||
console.error('健康检查失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例2: 获取示例提示词
|
||||
*/
|
||||
async function example2_GetSamplePrompts() {
|
||||
console.log('=== 获取示例提示词 ===')
|
||||
|
||||
try {
|
||||
const prompts = await textVideoAgentAPI.getSamplePrompt(TaskType.VLOG)
|
||||
console.log('VLOG类型示例提示词:', prompts)
|
||||
} catch (error) {
|
||||
console.error('获取提示词失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例3: 文件上传
|
||||
*/
|
||||
async function example3_FileUpload() {
|
||||
console.log('=== 文件上传示例 ===')
|
||||
|
||||
// 注意:这里需要实际的文件对象
|
||||
// const fileInput = document.getElementById('fileInput') as HTMLInputElement
|
||||
// const file = fileInput.files?.[0]
|
||||
|
||||
// 模拟文件对象(实际使用时替换为真实文件)
|
||||
const mockFile = new File(['mock content'], 'test.jpg', { type: 'image/jpeg' })
|
||||
|
||||
try {
|
||||
const uploadResult = await textVideoAgentAPI.uploadFile(mockFile)
|
||||
console.log('文件上传结果:', uploadResult)
|
||||
return uploadResult.data // 返回文件URL
|
||||
} catch (error) {
|
||||
console.error('文件上传失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 图片生成示例 ====================
|
||||
|
||||
/**
|
||||
* 示例4: 基础图片生成
|
||||
*/
|
||||
async function example4_BasicImageGeneration() {
|
||||
console.log('=== 基础图片生成示例 ===')
|
||||
|
||||
const params: ImageGenerationParams = {
|
||||
prompt: '一个美丽的女孩在喝茶,温馨的下午时光,自然光线,高质量摄影',
|
||||
max_wait_time: 120,
|
||||
poll_interval: 2
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await textVideoAgentAPI.generateImageSync(params)
|
||||
console.log('图片生成结果:', result)
|
||||
return result.data?.image_url
|
||||
} catch (error) {
|
||||
console.error('图片生成失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例5: 带参考图片的图片生成
|
||||
*/
|
||||
async function example5_ImageGenerationWithReference() {
|
||||
console.log('=== 带参考图片的图片生成示例 ===')
|
||||
|
||||
// 模拟参考图片文件
|
||||
const referenceImage = new File(['reference'], 'reference.jpg', { type: 'image/jpeg' })
|
||||
|
||||
const params: ImageGenerationParams = {
|
||||
prompt: '保持人物特征,改变背景为咖啡厅环境',
|
||||
img_file: referenceImage,
|
||||
...PRESET_CONFIGS.STANDARD
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await textVideoAgentAPI.generateImageWithRetry(params, 3)
|
||||
console.log('带参考图片生成结果:', result)
|
||||
return result.data?.image_url
|
||||
} catch (error) {
|
||||
console.error('带参考图片生成失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例6: 异步图片生成
|
||||
*/
|
||||
async function example6_AsyncImageGeneration() {
|
||||
console.log('=== 异步图片生成示例 ===')
|
||||
|
||||
const prompt = '现代简约风格的室内设计,明亮的客厅'
|
||||
|
||||
try {
|
||||
// 提交异步任务
|
||||
const taskResult = await textVideoAgentAPI.generateImageAsync(prompt)
|
||||
const taskId = taskResult.data?.task_id
|
||||
|
||||
if (!taskId) {
|
||||
throw new Error('未获取到任务ID')
|
||||
}
|
||||
|
||||
console.log('任务已提交,ID:', taskId)
|
||||
|
||||
// 轮询任务状态
|
||||
const finalResult = await textVideoAgentAPI.pollTaskUntilComplete(
|
||||
taskId,
|
||||
2000, // 2秒轮询间隔
|
||||
120000, // 2分钟超时
|
||||
(status) => {
|
||||
console.log('任务状态更新:', status)
|
||||
}
|
||||
)
|
||||
|
||||
console.log('异步图片生成完成:', finalResult)
|
||||
return finalResult.data?.image_url
|
||||
} catch (error) {
|
||||
console.error('异步图片生成失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 视频生成示例 ====================
|
||||
|
||||
/**
|
||||
* 示例7: 基础视频生成
|
||||
*/
|
||||
async function example7_BasicVideoGeneration() {
|
||||
console.log('=== 基础视频生成示例 ===')
|
||||
|
||||
// 首先生成一张图片作为视频的基础
|
||||
const imageUrl = await example4_BasicImageGeneration()
|
||||
|
||||
if (!imageUrl) {
|
||||
console.error('无法获取基础图片,跳过视频生成')
|
||||
return
|
||||
}
|
||||
|
||||
const params: VideoGenerationParams = {
|
||||
prompt: '女孩优雅地品茶,轻柔的动作,温馨的氛围',
|
||||
img_url: imageUrl,
|
||||
duration: VideoDuration.MEDIUM,
|
||||
max_wait_time: 300,
|
||||
poll_interval: 5
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await textVideoAgentAPI.generateVideoSync(params)
|
||||
console.log('视频生成结果:', result)
|
||||
return result.data?.video_url
|
||||
} catch (error) {
|
||||
console.error('视频生成失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例8: 异步视频生成
|
||||
*/
|
||||
async function example8_AsyncVideoGeneration() {
|
||||
console.log('=== 异步视频生成示例 ===')
|
||||
|
||||
const params: VideoGenerationParams = {
|
||||
prompt: '城市夜景延时摄影,车流如水,霓虹闪烁',
|
||||
duration: VideoDuration.LONG
|
||||
}
|
||||
|
||||
try {
|
||||
const taskResult = await textVideoAgentAPI.generateVideoAsync(params)
|
||||
const taskId = taskResult.data?.task_id
|
||||
|
||||
if (!taskId) {
|
||||
throw new Error('未获取到任务ID')
|
||||
}
|
||||
|
||||
console.log('视频生成任务已提交,ID:', taskId)
|
||||
|
||||
// 轮询任务状态
|
||||
const finalResult = await textVideoAgentAPI.pollTaskUntilComplete(
|
||||
taskId,
|
||||
5000, // 5秒轮询间隔
|
||||
600000, // 10分钟超时
|
||||
(status) => {
|
||||
console.log('视频生成进度:', status)
|
||||
}
|
||||
)
|
||||
|
||||
console.log('异步视频生成完成:', finalResult)
|
||||
return finalResult.data?.video_url
|
||||
} catch (error) {
|
||||
console.error('异步视频生成失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 高级功能示例 ====================
|
||||
|
||||
/**
|
||||
* 示例9: 图片描述功能
|
||||
*/
|
||||
async function example9_ImageDescription() {
|
||||
console.log('=== 图片描述示例 ===')
|
||||
|
||||
const imageUrl = 'https://example.com/sample-image.jpg'
|
||||
|
||||
try {
|
||||
const description = await textVideoAgentAPI.describeImageByUrl({
|
||||
image_url: imageUrl,
|
||||
max_wait_time: 60
|
||||
})
|
||||
|
||||
console.log('图片描述结果:', description)
|
||||
return description.data?.description
|
||||
} catch (error) {
|
||||
console.error('图片描述失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例10: 任务管理
|
||||
*/
|
||||
async function example10_TaskManagement() {
|
||||
console.log('=== 任务管理示例 ===')
|
||||
|
||||
const taskRequest: TaskRequest = {
|
||||
task_type: TaskType.VLOG,
|
||||
prompt: '制作一个关于健康生活方式的短视频',
|
||||
ar: AspectRatio.PORTRAIT
|
||||
}
|
||||
|
||||
try {
|
||||
// 创建任务
|
||||
const createResult = await textVideoAgentAPI.createTask(taskRequest)
|
||||
const taskId = createResult.data?.task_id
|
||||
|
||||
if (!taskId) {
|
||||
throw new Error('任务创建失败')
|
||||
}
|
||||
|
||||
console.log('任务创建成功,ID:', taskId)
|
||||
|
||||
// 查询任务状态
|
||||
const statusResult = await textVideoAgentAPI.getTaskStatusAsync(taskId)
|
||||
console.log('任务状态:', statusResult)
|
||||
|
||||
// 等待任务完成(同步方式)
|
||||
const finalResult = await textVideoAgentAPI.getTaskResultSync(taskId)
|
||||
console.log('任务最终结果:', finalResult)
|
||||
|
||||
return finalResult
|
||||
} catch (error) {
|
||||
console.error('任务管理失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例11: 端到端内容生成
|
||||
*/
|
||||
async function example11_EndToEndGeneration() {
|
||||
console.log('=== 端到端内容生成示例 ===')
|
||||
|
||||
try {
|
||||
const result = await textVideoAgentAPI.generateContentEndToEnd(
|
||||
'一个关于咖啡文化的精美内容,展现咖啡师的专业技艺',
|
||||
{
|
||||
taskType: TaskType.VLOG,
|
||||
aspectRatio: AspectRatio.PORTRAIT,
|
||||
videoDuration: VideoDuration.MEDIUM,
|
||||
generateVideo: true,
|
||||
onProgress: (step, progress) => {
|
||||
console.log(`进度更新: ${step} - ${progress}%`)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
console.log('端到端生成完成:', result)
|
||||
return result
|
||||
} catch (error) {
|
||||
console.error('端到端生成失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 批量处理示例 ====================
|
||||
|
||||
/**
|
||||
* 示例12: 批量图片生成
|
||||
*/
|
||||
async function example12_BatchImageGeneration() {
|
||||
console.log('=== 批量图片生成示例 ===')
|
||||
|
||||
const prompts = [
|
||||
'春天的樱花盛开',
|
||||
'夏日的海滩风光',
|
||||
'秋天的枫叶满山',
|
||||
'冬日的雪景如画'
|
||||
]
|
||||
|
||||
try {
|
||||
const results = await Promise.allSettled(
|
||||
prompts.map(async (prompt, index) => {
|
||||
const params: ImageGenerationParams = {
|
||||
prompt,
|
||||
...PRESET_CONFIGS.FAST // 使用快速配置
|
||||
}
|
||||
|
||||
console.log(`开始生成图片 ${index + 1}: ${prompt}`)
|
||||
const result = await textVideoAgentAPI.generateImageSync(params)
|
||||
console.log(`图片 ${index + 1} 生成完成`)
|
||||
|
||||
return {
|
||||
prompt,
|
||||
result: result.data?.image_url,
|
||||
success: result.status
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
console.log('批量图片生成结果:', results)
|
||||
return results
|
||||
} catch (error) {
|
||||
console.error('批量图片生成失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 主函数 ====================
|
||||
|
||||
/**
|
||||
* 运行所有示例
|
||||
*/
|
||||
async function runAllExamples() {
|
||||
console.log('开始运行 Text Video Agent API 示例...\n')
|
||||
|
||||
// 基础功能示例
|
||||
await example1_HealthCheck()
|
||||
await example2_GetSamplePrompts()
|
||||
|
||||
// 文件操作示例
|
||||
// await example3_FileUpload() // 需要实际文件
|
||||
|
||||
// 图片生成示例
|
||||
await example4_BasicImageGeneration()
|
||||
// await example5_ImageGenerationWithReference() // 需要实际文件
|
||||
await example6_AsyncImageGeneration()
|
||||
|
||||
// 视频生成示例
|
||||
await example7_BasicVideoGeneration()
|
||||
await example8_AsyncVideoGeneration()
|
||||
|
||||
// 高级功能示例
|
||||
await example9_ImageDescription()
|
||||
await example10_TaskManagement()
|
||||
await example11_EndToEndGeneration()
|
||||
|
||||
// 批量处理示例
|
||||
await example12_BatchImageGeneration()
|
||||
|
||||
console.log('\n所有示例运行完成!')
|
||||
}
|
||||
|
||||
// 导出示例函数
|
||||
export {
|
||||
example1_HealthCheck,
|
||||
example2_GetSamplePrompts,
|
||||
example3_FileUpload,
|
||||
example4_BasicImageGeneration,
|
||||
example5_ImageGenerationWithReference,
|
||||
example6_AsyncImageGeneration,
|
||||
example7_BasicVideoGeneration,
|
||||
example8_AsyncVideoGeneration,
|
||||
example9_ImageDescription,
|
||||
example10_TaskManagement,
|
||||
example11_EndToEndGeneration,
|
||||
example12_BatchImageGeneration,
|
||||
runAllExamples
|
||||
}
|
||||
|
||||
// 如果直接运行此文件,执行所有示例
|
||||
if (require.main === module) {
|
||||
runAllExamples().catch(console.error)
|
||||
}
|
||||
@@ -1,504 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
基于Typer开发规范的媒体管理器实现示例
|
||||
"""
|
||||
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Optional, List
|
||||
from enum import Enum
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
try:
|
||||
import typer
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
from rich.panel import Panel
|
||||
from rich.progress import Progress, SpinnerColumn, TextColumn
|
||||
except ImportError:
|
||||
print("❌ 需要安装 typer 和 rich: pip install typer rich")
|
||||
sys.exit(1)
|
||||
|
||||
from python_core.utils.progress import ProgressJSONRPCCommander
|
||||
from python_core.services.base import ProgressServiceBase
|
||||
|
||||
console = Console()
|
||||
|
||||
# 1. 类型定义(遵循规范)
|
||||
class OutputFormat(str, Enum):
|
||||
"""输出格式枚举"""
|
||||
JSON = "json"
|
||||
CSV = "csv"
|
||||
TXT = "txt"
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.value
|
||||
|
||||
class ProcessingMode(str, Enum):
|
||||
"""处理模式枚举"""
|
||||
FAST = "fast"
|
||||
BALANCED = "balanced"
|
||||
QUALITY = "quality"
|
||||
|
||||
# 2. 服务类(遵循规范)
|
||||
class MediaManagerService(ProgressServiceBase):
|
||||
"""媒体管理服务"""
|
||||
|
||||
def get_service_name(self) -> str:
|
||||
return "media_manager"
|
||||
|
||||
def upload_video(self, video_path: Path, tags: List[str] = None, filename: str = None) -> dict:
|
||||
"""上传单个视频"""
|
||||
if tags is None:
|
||||
tags = []
|
||||
|
||||
# 模拟上传过程
|
||||
steps = [
|
||||
"计算文件哈希...",
|
||||
"检查重复文件...",
|
||||
"复制文件到存储...",
|
||||
"提取视频信息...",
|
||||
"检测场景变化..."
|
||||
]
|
||||
|
||||
for step in steps:
|
||||
self.report_progress(step)
|
||||
time.sleep(0.3) # 模拟处理时间
|
||||
|
||||
result = {
|
||||
"video_path": str(video_path),
|
||||
"filename": filename or video_path.name,
|
||||
"tags": tags,
|
||||
"success": True,
|
||||
"scenes_detected": 3,
|
||||
"duration": 120.5
|
||||
}
|
||||
|
||||
# 保存结果到存储
|
||||
self.save_data("uploads", f"upload_{int(time.time())}", result)
|
||||
|
||||
return result
|
||||
|
||||
def batch_upload(self, directory: Path, tags: List[str] = None, recursive: bool = False) -> dict:
|
||||
"""批量上传视频"""
|
||||
if tags is None:
|
||||
tags = []
|
||||
|
||||
# 扫描视频文件
|
||||
video_extensions = {'.mp4', '.avi', '.mov', '.mkv', '.wmv'}
|
||||
video_files = []
|
||||
|
||||
if recursive:
|
||||
for ext in video_extensions:
|
||||
video_files.extend(directory.rglob(f"*{ext}"))
|
||||
else:
|
||||
for ext in video_extensions:
|
||||
video_files.extend(directory.glob(f"*{ext}"))
|
||||
|
||||
results = []
|
||||
for i, video_file in enumerate(video_files):
|
||||
self.report_progress(f"处理文件: {video_file.name} ({i+1}/{len(video_files)})")
|
||||
|
||||
try:
|
||||
result = self.upload_video(video_file, tags)
|
||||
results.append(result)
|
||||
except Exception as e:
|
||||
results.append({
|
||||
"video_path": str(video_file),
|
||||
"success": False,
|
||||
"error": str(e)
|
||||
})
|
||||
|
||||
batch_result = {
|
||||
"total_files": len(video_files),
|
||||
"successful": len([r for r in results if r.get("success")]),
|
||||
"failed": len([r for r in results if not r.get("success")]),
|
||||
"results": results
|
||||
}
|
||||
|
||||
# 保存批量结果
|
||||
self.save_data("batch_uploads", f"batch_{int(time.time())}", batch_result)
|
||||
|
||||
return batch_result
|
||||
|
||||
def get_recent_uploads(self, limit: int = 10) -> List[dict]:
|
||||
"""获取最近的上传记录"""
|
||||
keys = self.list_keys("uploads")
|
||||
recent_keys = sorted(keys)[-limit:]
|
||||
return list(self.load_batch_data("uploads", recent_keys).values())
|
||||
|
||||
# 3. Typer命令行接口(遵循规范)
|
||||
class MediaManagerCommander(ProgressJSONRPCCommander):
|
||||
"""基于Typer的媒体管理器命令行接口"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("media_manager")
|
||||
self.app = typer.Typer(
|
||||
name="media_manager",
|
||||
help="""
|
||||
🎬 媒体管理器
|
||||
|
||||
功能强大的视频处理和管理工具,支持:
|
||||
• 视频上传和元数据提取
|
||||
• 自动场景检测和分割
|
||||
• 批量处理和进度跟踪
|
||||
• 多种输出格式
|
||||
|
||||
使用 --help 查看具体命令帮助。
|
||||
""",
|
||||
rich_markup_mode="rich",
|
||||
no_args_is_help=True
|
||||
)
|
||||
self.service = MediaManagerService()
|
||||
self._setup_commands()
|
||||
|
||||
def _register_commands(self):
|
||||
"""注册命令(继承要求)"""
|
||||
pass # Typer通过装饰器自动注册
|
||||
|
||||
def _is_progressive_command(self, command: str) -> bool:
|
||||
"""判断是否需要进度报告"""
|
||||
return command in ["batch_upload", "analyze"]
|
||||
|
||||
def _execute_with_progress(self, command: str, args: dict):
|
||||
"""执行带进度的命令"""
|
||||
pass # 通过Typer命令直接处理
|
||||
|
||||
def _execute_simple_command(self, command: str, args: dict):
|
||||
"""执行简单命令"""
|
||||
pass # 通过Typer命令直接处理
|
||||
|
||||
def _setup_commands(self):
|
||||
"""设置Typer命令"""
|
||||
|
||||
@self.app.command()
|
||||
def upload(
|
||||
video_path: Path = typer.Argument(
|
||||
...,
|
||||
help="📹 视频文件路径",
|
||||
exists=True
|
||||
),
|
||||
tags: Optional[str] = typer.Option(
|
||||
None,
|
||||
"--tags", "-t",
|
||||
help="🏷️ 标签列表(逗号分隔)"
|
||||
),
|
||||
filename: Optional[str] = typer.Option(
|
||||
None,
|
||||
"--filename", "-f",
|
||||
help="📝 自定义文件名"
|
||||
),
|
||||
verbose: bool = typer.Option(
|
||||
False,
|
||||
"--verbose", "-v",
|
||||
help="📝 详细输出"
|
||||
)
|
||||
):
|
||||
"""
|
||||
📤 上传视频文件
|
||||
|
||||
上传单个视频文件到媒体库,自动进行场景检测和元数据提取。
|
||||
|
||||
示例:
|
||||
media_manager upload video.mp4 --tags "demo,test"
|
||||
media_manager upload /path/to/video.mp4 --filename "my_video"
|
||||
|
||||
注意:
|
||||
- 支持的格式: MP4, AVI, MOV, MKV, WMV
|
||||
- 自动检测重复文件
|
||||
- 自动提取视频元数据
|
||||
"""
|
||||
# 参数验证
|
||||
if not video_path.exists():
|
||||
console.print("❌ [red]视频文件不存在[/red]")
|
||||
raise typer.Exit(1)
|
||||
|
||||
# 解析标签
|
||||
tag_list = []
|
||||
if tags:
|
||||
tag_list = [tag.strip() for tag in tags.split(",")]
|
||||
|
||||
# 显示开始信息
|
||||
console.print(f"🚀 开始上传: [bold blue]{video_path}[/bold blue]")
|
||||
|
||||
try:
|
||||
# 设置进度回调
|
||||
def progress_callback(message: str):
|
||||
console.print(f"📊 {message}")
|
||||
|
||||
self.service.set_progress_callback(progress_callback)
|
||||
|
||||
# 执行上传
|
||||
result = self.service.upload_video(video_path, tag_list, filename)
|
||||
|
||||
# 显示结果
|
||||
console.print("✅ [bold green]上传完成[/bold green]")
|
||||
|
||||
if verbose or True: # 总是显示基本信息
|
||||
self._show_upload_result(result)
|
||||
|
||||
except Exception as e:
|
||||
console.print(f"❌ [red]上传失败: {e}[/red]")
|
||||
if verbose:
|
||||
console.print_exception()
|
||||
raise typer.Exit(1)
|
||||
|
||||
@self.app.command()
|
||||
def batch_upload(
|
||||
input_directory: Path = typer.Argument(
|
||||
...,
|
||||
help="📁 输入目录路径",
|
||||
exists=True,
|
||||
file_okay=False
|
||||
),
|
||||
output_path: Optional[Path] = typer.Option(
|
||||
None,
|
||||
"--output", "-o",
|
||||
help="📄 结果输出文件路径"
|
||||
),
|
||||
tags: Optional[str] = typer.Option(
|
||||
None,
|
||||
"--tags", "-t",
|
||||
help="🏷️ 标签列表(逗号分隔)"
|
||||
),
|
||||
recursive: bool = typer.Option(
|
||||
False,
|
||||
"--recursive", "-r",
|
||||
help="🔄 递归处理子目录"
|
||||
),
|
||||
output_format: OutputFormat = typer.Option(
|
||||
OutputFormat.JSON,
|
||||
"--format", "-f",
|
||||
help="📄 输出格式"
|
||||
)
|
||||
):
|
||||
"""
|
||||
📦 批量上传视频文件(带进度条)
|
||||
|
||||
批量处理目录中的所有视频文件,支持递归扫描和进度跟踪。
|
||||
|
||||
示例:
|
||||
media_manager batch-upload /videos --tags "batch,demo"
|
||||
media_manager batch-upload /videos -r --output results.json
|
||||
"""
|
||||
console.print(f"📦 批量上传目录: [bold blue]{input_directory}[/bold blue]")
|
||||
|
||||
# 解析标签
|
||||
tag_list = []
|
||||
if tags:
|
||||
tag_list = [tag.strip() for tag in tags.split(",")]
|
||||
|
||||
# 扫描文件数量
|
||||
video_extensions = {'.mp4', '.avi', '.mov', '.mkv', '.wmv'}
|
||||
video_files = []
|
||||
|
||||
if recursive:
|
||||
for ext in video_extensions:
|
||||
video_files.extend(input_directory.rglob(f"*{ext}"))
|
||||
else:
|
||||
for ext in video_extensions:
|
||||
video_files.extend(input_directory.glob(f"*{ext}"))
|
||||
|
||||
if not video_files:
|
||||
console.print("⚠️ [yellow]未找到可处理的视频文件[/yellow]")
|
||||
return
|
||||
|
||||
console.print(f"📋 找到 {len(video_files)} 个视频文件")
|
||||
|
||||
# 使用进度任务
|
||||
with self.create_task("批量上传", len(video_files)) as task:
|
||||
def progress_callback(message: str):
|
||||
# 从消息中提取文件信息更新任务
|
||||
if "处理文件:" in message:
|
||||
task.update(message=message)
|
||||
|
||||
self.service.set_progress_callback(progress_callback)
|
||||
|
||||
# 执行批量上传
|
||||
result = self.service.batch_upload(input_directory, tag_list, recursive)
|
||||
|
||||
task.finish(f"批量上传完成: {result['successful']}/{result['total_files']} 成功")
|
||||
|
||||
# 显示结果摘要
|
||||
self._show_batch_result(result)
|
||||
|
||||
# 保存结果文件
|
||||
if output_path:
|
||||
self._save_batch_results(result, output_path, output_format)
|
||||
console.print(f"📄 结果已保存到: {output_path}")
|
||||
|
||||
@self.app.command()
|
||||
def list_uploads(
|
||||
limit: int = typer.Option(
|
||||
10,
|
||||
"--limit", "-l",
|
||||
min=1,
|
||||
max=100,
|
||||
help="📊 显示数量限制"
|
||||
),
|
||||
verbose: bool = typer.Option(
|
||||
False,
|
||||
"--verbose", "-v",
|
||||
help="📝 详细信息"
|
||||
)
|
||||
):
|
||||
"""
|
||||
📋 列出最近的上传记录
|
||||
|
||||
显示最近上传的视频文件信息和统计数据。
|
||||
"""
|
||||
console.print("📋 最近的上传记录")
|
||||
|
||||
try:
|
||||
uploads = self.service.get_recent_uploads(limit)
|
||||
|
||||
if not uploads:
|
||||
console.print("⚠️ [yellow]暂无上传记录[/yellow]")
|
||||
return
|
||||
|
||||
self._show_uploads_table(uploads, verbose)
|
||||
|
||||
except Exception as e:
|
||||
console.print(f"❌ [red]获取记录失败: {e}[/red]")
|
||||
raise typer.Exit(1)
|
||||
|
||||
@self.app.command()
|
||||
def status():
|
||||
"""
|
||||
📊 显示系统状态
|
||||
|
||||
显示媒体管理器的当前状态和统计信息。
|
||||
"""
|
||||
console.print("📊 媒体管理器状态")
|
||||
|
||||
try:
|
||||
# 获取统计信息
|
||||
uploads_stats = self.service.get_collection_stats("uploads")
|
||||
batch_stats = self.service.get_collection_stats("batch_uploads")
|
||||
|
||||
# 创建状态表格
|
||||
table = Table(title="系统状态")
|
||||
table.add_column("组件", style="cyan")
|
||||
table.add_column("状态", style="green")
|
||||
table.add_column("详情", style="yellow")
|
||||
|
||||
table.add_row("存储", "✅ 正常", f"JSON文件存储")
|
||||
table.add_row("上传记录", "✅ 活跃", f"{uploads_stats.get('file_count', 0)} 条记录")
|
||||
table.add_row("批量任务", "✅ 就绪", f"{batch_stats.get('file_count', 0)} 个任务")
|
||||
table.add_row("进度系统", "✅ 就绪", "JSON-RPC协议")
|
||||
|
||||
console.print(table)
|
||||
|
||||
# 创建信息面板
|
||||
info_panel = Panel(
|
||||
"[bold blue]媒体管理器运行正常[/bold blue]\n"
|
||||
"所有组件状态良好,可以开始处理视频文件。",
|
||||
title="📊 状态摘要",
|
||||
border_style="green"
|
||||
)
|
||||
console.print(info_panel)
|
||||
|
||||
except Exception as e:
|
||||
console.print(f"❌ [red]获取状态失败: {e}[/red]")
|
||||
raise typer.Exit(1)
|
||||
|
||||
def _show_upload_result(self, result: dict):
|
||||
"""显示上传结果"""
|
||||
table = Table(title="📤 上传结果")
|
||||
table.add_column("项目", style="cyan")
|
||||
table.add_column("值", style="green")
|
||||
|
||||
table.add_row("文件名", result["filename"])
|
||||
table.add_row("标签", ", ".join(result["tags"]) if result["tags"] else "无")
|
||||
table.add_row("检测场景", str(result["scenes_detected"]))
|
||||
table.add_row("视频时长", f"{result['duration']:.1f}秒")
|
||||
|
||||
console.print(table)
|
||||
|
||||
def _show_batch_result(self, result: dict):
|
||||
"""显示批量结果摘要"""
|
||||
panel = Panel(
|
||||
f"[bold green]总文件: {result['total_files']}[/bold green]\n"
|
||||
f"[bold blue]成功: {result['successful']}[/bold blue]\n"
|
||||
f"[bold red]失败: {result['failed']}[/bold red]",
|
||||
title="📦 批量上传摘要",
|
||||
border_style="green"
|
||||
)
|
||||
console.print(panel)
|
||||
|
||||
def _show_uploads_table(self, uploads: List[dict], verbose: bool):
|
||||
"""显示上传记录表格"""
|
||||
table = Table(title="📋 上传记录")
|
||||
table.add_column("文件名", style="cyan")
|
||||
table.add_column("场景数", style="green")
|
||||
table.add_column("时长", style="yellow")
|
||||
|
||||
if verbose:
|
||||
table.add_column("标签", style="magenta")
|
||||
|
||||
for upload in uploads:
|
||||
row = [
|
||||
upload["filename"],
|
||||
str(upload.get("scenes_detected", "N/A")),
|
||||
f"{upload.get('duration', 0):.1f}s"
|
||||
]
|
||||
|
||||
if verbose:
|
||||
tags = ", ".join(upload.get("tags", [])) or "无"
|
||||
row.append(tags)
|
||||
|
||||
table.add_row(*row)
|
||||
|
||||
console.print(table)
|
||||
|
||||
def _save_batch_results(self, result: dict, output_path: Path, format: OutputFormat):
|
||||
"""保存批量结果"""
|
||||
if format == OutputFormat.JSON:
|
||||
import json
|
||||
with open(output_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(result, f, indent=2, ensure_ascii=False)
|
||||
elif format == OutputFormat.CSV:
|
||||
import csv
|
||||
with open(output_path, 'w', newline='', encoding='utf-8') as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow(['filename', 'success', 'scenes', 'duration', 'error'])
|
||||
for item in result['results']:
|
||||
writer.writerow([
|
||||
item.get('filename', ''),
|
||||
item.get('success', False),
|
||||
item.get('scenes_detected', ''),
|
||||
item.get('duration', ''),
|
||||
item.get('error', '')
|
||||
])
|
||||
else: # TXT
|
||||
with open(output_path, 'w', encoding='utf-8') as f:
|
||||
f.write(f"批量上传结果\n")
|
||||
f.write(f"总文件数: {result['total_files']}\n")
|
||||
f.write(f"成功: {result['successful']}\n")
|
||||
f.write(f"失败: {result['failed']}\n\n")
|
||||
|
||||
for item in result['results']:
|
||||
f.write(f"文件: {item.get('filename', '')}\n")
|
||||
f.write(f" 状态: {'成功' if item.get('success') else '失败'}\n")
|
||||
if item.get('success'):
|
||||
f.write(f" 场景数: {item.get('scenes_detected', 'N/A')}\n")
|
||||
f.write(f" 时长: {item.get('duration', 0):.1f}秒\n")
|
||||
else:
|
||||
f.write(f" 错误: {item.get('error', '')}\n")
|
||||
f.write("\n")
|
||||
|
||||
def run(self):
|
||||
"""运行CLI"""
|
||||
self.app()
|
||||
|
||||
def main():
|
||||
"""主入口函数"""
|
||||
commander = MediaManagerCommander()
|
||||
commander.run()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user