mirror of
https://github.com/zhouxiaoka/autoclip.git
synced 2026-09-03 06:24:14 +08:00
修复WebSocket进度更新和前端状态自动更新问题
- 修复WebSocketNotificationService.send_processing_progress方法参数不匹配问题 - 修复前端RealTimeStatus组件WebSocket消息处理逻辑 - 修复Celery Worker队列配置,确保任务正确路由到processing队列 - 修复Celery应用导入冲突,统一使用正确的celery_app配置 - 添加实时项目状态更新功能,前端无需手动刷新即可看到处理进度 - 完善系统启动脚本,修复PYTHONPATH未绑定变量错误 - 优化流水线处理逻辑,确保所有6个步骤正常执行 - 添加完整的项目文档和启动指南 测试结果: - WebSocket进度更新正常工作(16%, 33%, 100%) - 流水线处理完全正常(6个步骤全部成功) - 前端状态自动更新正常 - 项目状态正确同步到数据库
This commit is contained in:
@@ -1,39 +0,0 @@
|
||||
# 调试说明
|
||||
|
||||
## 问题描述
|
||||
前端显示项目有5个切片,但进入项目详情页时看不到切片数据。
|
||||
|
||||
## 调试步骤
|
||||
|
||||
1. **打开浏览器开发者工具**
|
||||
- 在Chrome中按F12或右键 -> 检查
|
||||
- 切换到"Console"标签页
|
||||
|
||||
2. **访问项目详情页面**
|
||||
- 访问: http://localhost:3000
|
||||
- 点击进入项目: "投资亏损别甩锅,认知水平才是决定成败的关键"
|
||||
|
||||
3. **查看控制台输出**
|
||||
查找以下日志信息:
|
||||
```
|
||||
🔍 Calling clips API for project: 1fdb0bf1-7f3c-44f7-a69d-90c5a1d26fbe
|
||||
📦 Raw API response: {...}
|
||||
📋 Extracted clips: X clips found
|
||||
✅ Converted clips: X clips
|
||||
📄 First clip sample: {...}
|
||||
🎬 Loaded clips in ProjectDetailPage: [...]
|
||||
📚 Loaded collections in ProjectDetailPage: [...]
|
||||
🎯 Final project with data: {...}
|
||||
```
|
||||
|
||||
4. **API验证**
|
||||
可以直接在浏览器中访问API:
|
||||
http://localhost:8000/api/v1/clips/?project_id=1fdb0bf1-7f3c-44f7-a69d-90c5a1d26fbe
|
||||
|
||||
## 期望结果
|
||||
- API应该返回5个切片
|
||||
- 前端应该正确显示这5个切片
|
||||
|
||||
## 如果仍然有问题
|
||||
请提供控制台的完整日志输出,特别是任何错误信息。
|
||||
|
||||
@@ -1,215 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
数据一致性检查工具
|
||||
检查clip数据、文件路径和标题的对应关系
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from typing import Dict, List, Any
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
import sys
|
||||
sys.path.insert(0, str(project_root / "backend"))
|
||||
|
||||
from core.database import SessionLocal
|
||||
from models.clip import Clip
|
||||
from models.project import Project
|
||||
from core.path_utils import get_clips_directory, get_project_directory
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def check_clip_data_consistency(project_id: str = None):
|
||||
"""检查clip数据一致性"""
|
||||
print("🔍 开始检查数据一致性...")
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
# 获取所有项目或指定项目
|
||||
if project_id:
|
||||
projects = [db.query(Project).filter(Project.id == project_id).first()]
|
||||
if not projects[0]:
|
||||
print(f"❌ 项目不存在: {project_id}")
|
||||
return
|
||||
else:
|
||||
projects = db.query(Project).all()
|
||||
|
||||
total_issues = 0
|
||||
|
||||
for project in projects:
|
||||
print(f"\n📁 检查项目: {project.name} ({project.id})")
|
||||
project_issues = check_project_clips(project, db)
|
||||
total_issues += project_issues
|
||||
|
||||
if project_issues == 0:
|
||||
print(f"✅ 项目 {project.name} 数据一致")
|
||||
else:
|
||||
print(f"⚠️ 项目 {project.name} 发现 {project_issues} 个问题")
|
||||
|
||||
print(f"\n📊 检查完成,总共发现 {total_issues} 个问题")
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
def check_project_clips(project: Project, db) -> int:
|
||||
"""检查单个项目的clip数据"""
|
||||
issues = 0
|
||||
clips = db.query(Clip).filter(Clip.project_id == project.id).all()
|
||||
|
||||
print(f" 📋 找到 {len(clips)} 个切片")
|
||||
|
||||
# 检查文件系统
|
||||
clips_dir = get_clips_dir()
|
||||
if not clips_dir.exists():
|
||||
print(f" ❌ Clips目录不存在: {clips_dir}")
|
||||
return len(clips) # 所有clip都有问题
|
||||
|
||||
# 检查每个clip
|
||||
for clip in clips:
|
||||
clip_issues = check_single_clip(clip, clips_dir)
|
||||
issues += clip_issues
|
||||
|
||||
if clip_issues > 0:
|
||||
print(f" ⚠️ Clip {clip.id}: {clip.title}")
|
||||
print(f" 问题: {clip_issues} 个")
|
||||
|
||||
return issues
|
||||
|
||||
def check_single_clip(clip: Clip, clips_dir: Path) -> int:
|
||||
"""检查单个clip的数据一致性"""
|
||||
issues = 0
|
||||
|
||||
# 1. 检查标题是否为空
|
||||
if not clip.title or clip.title.strip() == "":
|
||||
print(f" - 标题为空")
|
||||
issues += 1
|
||||
|
||||
# 2. 检查文件路径
|
||||
if not clip.video_path:
|
||||
print(f" - 数据库中没有视频文件路径")
|
||||
issues += 1
|
||||
else:
|
||||
video_file = Path(clip.video_path)
|
||||
if not video_file.exists():
|
||||
print(f" - 视频文件不存在: {video_file}")
|
||||
issues += 1
|
||||
|
||||
# 3. 检查文件系统中的文件
|
||||
expected_files = list(clips_dir.glob(f"{clip.id}_*.mp4"))
|
||||
if not expected_files:
|
||||
print(f" - 文件系统中找不到对应的视频文件")
|
||||
issues += 1
|
||||
elif len(expected_files) > 1:
|
||||
print(f" - 找到多个匹配的文件: {[f.name for f in expected_files]}")
|
||||
issues += 1
|
||||
else:
|
||||
# 检查文件名是否包含标题
|
||||
file_name = expected_files[0].name
|
||||
if clip.title and clip.title not in file_name:
|
||||
print(f" - 文件名不包含标题: {file_name}")
|
||||
issues += 1
|
||||
|
||||
# 4. 检查元数据
|
||||
if not clip.clip_metadata:
|
||||
print(f" - 没有元数据")
|
||||
issues += 1
|
||||
else:
|
||||
# 检查元数据文件是否存在
|
||||
metadata_file = clip.clip_metadata.get('metadata_file')
|
||||
if metadata_file and not Path(metadata_file).exists():
|
||||
print(f" - 元数据文件不存在: {metadata_file}")
|
||||
issues += 1
|
||||
|
||||
return issues
|
||||
|
||||
def get_clips_dir() -> Path:
|
||||
"""获取clips目录"""
|
||||
try:
|
||||
return get_clips_directory()
|
||||
except Exception as e:
|
||||
logger.error(f"获取clips目录失败: {e}")
|
||||
return Path("data/output/clips")
|
||||
|
||||
def fix_clip_data_issues(project_id: str = None):
|
||||
"""修复clip数据问题"""
|
||||
print("🔧 开始修复数据问题...")
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
# 获取所有项目或指定项目
|
||||
if project_id:
|
||||
projects = [db.query(Project).filter(Project.id == project_id).first()]
|
||||
if not projects[0]:
|
||||
print(f"❌ 项目不存在: {project_id}")
|
||||
return
|
||||
else:
|
||||
projects = db.query(Project).all()
|
||||
|
||||
for project in projects:
|
||||
print(f"\n🔧 修复项目: {project.name} ({project.id})")
|
||||
fix_project_clips(project, db)
|
||||
|
||||
print("\n✅ 修复完成")
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
def fix_project_clips(project: Project, db):
|
||||
"""修复单个项目的clip数据"""
|
||||
clips = db.query(Clip).filter(Clip.project_id == project.id).all()
|
||||
clips_dir = get_clips_dir()
|
||||
|
||||
for clip in clips:
|
||||
fix_single_clip(clip, clips_dir, db)
|
||||
|
||||
db.commit()
|
||||
|
||||
def fix_single_clip(clip: Clip, clips_dir: Path, db):
|
||||
"""修复单个clip的数据问题"""
|
||||
# 1. 修复空标题
|
||||
if not clip.title or clip.title.strip() == "":
|
||||
# 尝试从元数据中获取标题
|
||||
if clip.clip_metadata:
|
||||
generated_title = clip.clip_metadata.get('generated_title')
|
||||
if generated_title:
|
||||
clip.title = generated_title
|
||||
print(f" ✅ 修复clip {clip.id} 的标题: {generated_title}")
|
||||
else:
|
||||
clip.title = f"Clip_{clip.id[:8]}"
|
||||
print(f" ✅ 为clip {clip.id} 设置默认标题: {clip.title}")
|
||||
|
||||
# 2. 修复文件路径
|
||||
if not clip.video_path:
|
||||
# 查找对应的文件
|
||||
expected_files = list(clips_dir.glob(f"{clip.id}_*.mp4"))
|
||||
if expected_files:
|
||||
clip.video_path = str(expected_files[0])
|
||||
print(f" ✅ 修复clip {clip.id} 的文件路径: {clip.video_path}")
|
||||
|
||||
# 3. 修复元数据
|
||||
if not clip.clip_metadata:
|
||||
clip.clip_metadata = {
|
||||
'clip_id': clip.id,
|
||||
'created_at': clip.created_at.isoformat() if clip.created_at else None
|
||||
}
|
||||
print(f" ✅ 为clip {clip.id} 创建基础元数据")
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="数据一致性检查工具")
|
||||
parser.add_argument("--project-id", help="指定项目ID")
|
||||
parser.add_argument("--fix", action="store_true", help="修复发现的问题")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.fix:
|
||||
fix_clip_data_issues(args.project_id)
|
||||
else:
|
||||
check_clip_data_consistency(args.project_id)
|
||||
@@ -1,118 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
检查前端可能遇到的所有问题
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
|
||||
def check_frontend_issues():
|
||||
"""检查前端可能遇到的所有问题"""
|
||||
|
||||
print("🔍 检查前端可能遇到的所有问题")
|
||||
print("=" * 60)
|
||||
|
||||
# 1. 检查后端服务状态
|
||||
print("\n1️⃣ 检查后端服务状态...")
|
||||
try:
|
||||
response = requests.get("http://localhost:8000/health")
|
||||
print(f"📥 健康检查: {response.status_code}")
|
||||
if response.status_code == 200:
|
||||
print("✅ 后端服务正常")
|
||||
else:
|
||||
print("❌ 后端服务异常")
|
||||
except Exception as e:
|
||||
print(f"❌ 后端服务连接失败: {e}")
|
||||
return
|
||||
|
||||
# 2. 检查前端服务状态
|
||||
print("\n2️⃣ 检查前端服务状态...")
|
||||
try:
|
||||
response = requests.get("http://localhost:3000")
|
||||
print(f"📥 前端服务: {response.status_code}")
|
||||
if response.status_code == 200:
|
||||
print("✅ 前端服务正常")
|
||||
else:
|
||||
print("❌ 前端服务异常")
|
||||
except Exception as e:
|
||||
print(f"❌ 前端服务连接失败: {e}")
|
||||
print("💡 前端服务可能没有启动")
|
||||
|
||||
# 3. 检查API端点
|
||||
print("\n3️⃣ 检查API端点...")
|
||||
endpoints = [
|
||||
"/api/v1/collections/0e181e1a-52c2-42c2-9481-cc306e3b27f9",
|
||||
"/api/v1/collections/0e181e1a-52c2-42c2-9481-cc306e3b27f9/reorder",
|
||||
"/api/v1/projects/5c48803d-0aa7-48d7-a270-2b33e4954f25"
|
||||
]
|
||||
|
||||
for endpoint in endpoints:
|
||||
try:
|
||||
response = requests.get(f"http://localhost:8000{endpoint}")
|
||||
print(f"📥 {endpoint}: {response.status_code}")
|
||||
if response.status_code in [200, 404]:
|
||||
print(f"✅ {endpoint} 可访问")
|
||||
else:
|
||||
print(f"❌ {endpoint} 异常")
|
||||
except Exception as e:
|
||||
print(f"❌ {endpoint} 连接失败: {e}")
|
||||
|
||||
# 4. 检查CORS设置
|
||||
print("\n4️⃣ 检查CORS设置...")
|
||||
try:
|
||||
response = requests.options(
|
||||
"http://localhost:8000/api/v1/collections/0e181e1a-52c2-42c2-9481-cc306e3b27f9/reorder",
|
||||
headers={
|
||||
"Origin": "http://localhost:3000",
|
||||
"Access-Control-Request-Method": "PATCH",
|
||||
"Access-Control-Request-Headers": "Content-Type"
|
||||
}
|
||||
)
|
||||
print(f"📥 CORS预检: {response.status_code}")
|
||||
cors_headers = {k: v for k, v in response.headers.items() if k.lower().startswith('access-control')}
|
||||
print(f"📥 CORS头: {cors_headers}")
|
||||
if response.status_code == 200:
|
||||
print("✅ CORS设置正常")
|
||||
else:
|
||||
print("❌ CORS设置异常")
|
||||
except Exception as e:
|
||||
print(f"❌ CORS检查失败: {e}")
|
||||
|
||||
# 5. 检查网络连接
|
||||
print("\n5️⃣ 检查网络连接...")
|
||||
try:
|
||||
response = requests.get("http://localhost:8000/api/v1/projects/", timeout=5)
|
||||
print(f"📥 网络连接: {response.status_code}")
|
||||
if response.status_code == 200:
|
||||
print("✅ 网络连接正常")
|
||||
else:
|
||||
print("❌ 网络连接异常")
|
||||
except requests.exceptions.Timeout:
|
||||
print("❌ 网络连接超时")
|
||||
except Exception as e:
|
||||
print(f"❌ 网络连接失败: {e}")
|
||||
|
||||
# 6. 检查数据库连接
|
||||
print("\n6️⃣ 检查数据库连接...")
|
||||
try:
|
||||
response = requests.get("http://localhost:8000/api/v1/collections/0e181e1a-52c2-42c2-9481-cc306e3b27f9")
|
||||
print(f"📥 数据库查询: {response.status_code}")
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
print(f"✅ 数据库连接正常,合集: {data.get('name', 'Unknown')}")
|
||||
else:
|
||||
print(f"❌ 数据库查询失败: {response.text}")
|
||||
except Exception as e:
|
||||
print(f"❌ 数据库连接失败: {e}")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("🎉 前端问题检查完成!")
|
||||
print("\n💡 如果后端API正常但前端仍然失败,可能的原因:")
|
||||
print("1. 前端JavaScript错误 - 请检查浏览器控制台")
|
||||
print("2. 前端缓存问题 - 请清除浏览器缓存或重启前端服务")
|
||||
print("3. 前端使用了旧版本的代码 - 请重新构建前端")
|
||||
print("4. 前端网络请求被拦截 - 请检查浏览器网络面板")
|
||||
print("5. 前端组件没有正确绑定事件 - 请检查拖拽组件")
|
||||
|
||||
if __name__ == "__main__":
|
||||
check_frontend_issues()
|
||||
@@ -1,478 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
存储优化实施检查清单
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any, List
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
# 添加backend目录到路径
|
||||
backend_dir = project_root / "backend"
|
||||
sys.path.insert(0, str(backend_dir))
|
||||
|
||||
def check_database_models():
|
||||
"""检查数据库模型优化状态"""
|
||||
print("🔍 检查数据库模型优化状态...")
|
||||
|
||||
checklist = {
|
||||
"Clip模型优化": {
|
||||
"移除processing_result字段": False,
|
||||
"优化clip_metadata字段": False,
|
||||
"保留video_path字段": False,
|
||||
"保留thumbnail_path字段": False
|
||||
},
|
||||
"Project模型优化": {
|
||||
"添加video_path字段": False,
|
||||
"添加subtitle_path字段": False,
|
||||
"优化project_metadata字段": False
|
||||
},
|
||||
"Collection模型优化": {
|
||||
"添加export_path字段": False,
|
||||
"优化collection_metadata字段": False
|
||||
}
|
||||
}
|
||||
|
||||
# 检查Clip模型
|
||||
try:
|
||||
from models.clip import Clip
|
||||
clip_columns = [col.name for col in Clip.__table__.columns]
|
||||
|
||||
if "processing_result" not in clip_columns:
|
||||
checklist["Clip模型优化"]["移除processing_result字段"] = True
|
||||
|
||||
if "video_path" in clip_columns:
|
||||
checklist["Clip模型优化"]["保留video_path字段"] = True
|
||||
|
||||
if "thumbnail_path" in clip_columns:
|
||||
checklist["Clip模型优化"]["保留thumbnail_path字段"] = True
|
||||
|
||||
# 检查计算属性
|
||||
if hasattr(Clip, 'metadata_file_path'):
|
||||
checklist["Clip模型优化"]["优化clip_metadata字段"] = True
|
||||
|
||||
except ImportError:
|
||||
print(" ❌ 无法导入Clip模型")
|
||||
|
||||
# 检查Project模型
|
||||
try:
|
||||
from models.project import Project
|
||||
project_columns = [col.name for col in Project.__table__.columns]
|
||||
|
||||
if "video_path" in project_columns:
|
||||
checklist["Project模型优化"]["添加video_path字段"] = True
|
||||
|
||||
if "subtitle_path" in project_columns:
|
||||
checklist["Project模型优化"]["添加subtitle_path字段"] = True
|
||||
|
||||
# 检查计算属性
|
||||
if hasattr(Project, 'storage_initialized'):
|
||||
checklist["Project模型优化"]["优化project_metadata字段"] = True
|
||||
|
||||
except ImportError:
|
||||
print(" ❌ 无法导入Project模型")
|
||||
|
||||
# 检查Collection模型
|
||||
try:
|
||||
from models.collection import Collection
|
||||
collection_columns = [col.name for col in Collection.__table__.columns]
|
||||
|
||||
if "export_path" in collection_columns:
|
||||
checklist["Collection模型优化"]["添加export_path字段"] = True
|
||||
|
||||
# 检查计算属性
|
||||
if hasattr(Collection, 'metadata_file_path'):
|
||||
checklist["Collection模型优化"]["优化collection_metadata字段"] = True
|
||||
|
||||
except ImportError:
|
||||
print(" ❌ 无法导入Collection模型")
|
||||
|
||||
return checklist
|
||||
|
||||
def check_storage_service():
|
||||
"""检查存储服务状态"""
|
||||
print("\n🔍 检查存储服务状态...")
|
||||
|
||||
checklist = {
|
||||
"StorageService": {
|
||||
"文件存在": False,
|
||||
"save_metadata方法": False,
|
||||
"save_file方法": False,
|
||||
"get_file_path方法": False,
|
||||
"cleanup_temp_files方法": False
|
||||
}
|
||||
}
|
||||
|
||||
storage_service_path = backend_dir / "services" / "storage_service.py"
|
||||
|
||||
if storage_service_path.exists():
|
||||
checklist["StorageService"]["文件存在"] = True
|
||||
|
||||
# 读取文件内容检查方法
|
||||
with open(storage_service_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
if "def save_metadata" in content:
|
||||
checklist["StorageService"]["save_metadata方法"] = True
|
||||
|
||||
if "def save_file" in content:
|
||||
checklist["StorageService"]["save_file方法"] = True
|
||||
|
||||
if "def get_file_path" in content:
|
||||
checklist["StorageService"]["get_file_path方法"] = True
|
||||
|
||||
if "def cleanup_temp_files" in content:
|
||||
checklist["StorageService"]["cleanup_temp_files方法"] = True
|
||||
|
||||
return checklist
|
||||
|
||||
def check_pipeline_adapter():
|
||||
"""检查PipelineAdapter优化状态"""
|
||||
print("\n🔍 检查PipelineAdapter优化状态...")
|
||||
|
||||
checklist = {
|
||||
"PipelineAdapter": {
|
||||
"文件存在": False,
|
||||
"使用StorageService": False,
|
||||
"分离存储逻辑": False
|
||||
}
|
||||
}
|
||||
|
||||
pipeline_adapter_path = backend_dir / "services" / "pipeline_adapter.py"
|
||||
|
||||
if pipeline_adapter_path.exists():
|
||||
checklist["PipelineAdapter"]["文件存在"] = True
|
||||
|
||||
# 读取文件内容检查
|
||||
with open(pipeline_adapter_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
if "StorageService" in content:
|
||||
checklist["PipelineAdapter"]["使用StorageService"] = True
|
||||
|
||||
if "_save_clips_to_database" in content and "_save_collections_to_database" in content:
|
||||
checklist["PipelineAdapter"]["分离存储逻辑"] = True
|
||||
|
||||
return checklist
|
||||
|
||||
def check_repositories():
|
||||
"""检查Repository层优化状态"""
|
||||
print("\n🔍 检查Repository层优化状态...")
|
||||
|
||||
checklist = {
|
||||
"ClipRepository": {
|
||||
"文件存在": False,
|
||||
"分离存储方法": False,
|
||||
"文件访问方法": False
|
||||
},
|
||||
"CollectionRepository": {
|
||||
"文件存在": False,
|
||||
"分离存储方法": False,
|
||||
"文件访问方法": False
|
||||
},
|
||||
"ProjectRepository": {
|
||||
"文件存在": False,
|
||||
"文件路径管理": False
|
||||
}
|
||||
}
|
||||
|
||||
# 检查ClipRepository
|
||||
clip_repo_path = backend_dir / "repositories" / "clip_repository.py"
|
||||
if clip_repo_path.exists():
|
||||
checklist["ClipRepository"]["文件存在"] = True
|
||||
|
||||
with open(clip_repo_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
if "get_clip_file" in content:
|
||||
checklist["ClipRepository"]["文件访问方法"] = True
|
||||
|
||||
if "create_clip" in content:
|
||||
checklist["ClipRepository"]["分离存储方法"] = True
|
||||
|
||||
# 检查CollectionRepository
|
||||
collection_repo_path = backend_dir / "repositories" / "collection_repository.py"
|
||||
if collection_repo_path.exists():
|
||||
checklist["CollectionRepository"]["文件存在"] = True
|
||||
|
||||
with open(collection_repo_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
if "get_collection_file" in content:
|
||||
checklist["CollectionRepository"]["文件访问方法"] = True
|
||||
|
||||
if "create_collection" in content:
|
||||
checklist["CollectionRepository"]["分离存储方法"] = True
|
||||
|
||||
# 检查ProjectRepository
|
||||
project_repo_path = backend_dir / "repositories" / "project_repository.py"
|
||||
if project_repo_path.exists():
|
||||
checklist["ProjectRepository"]["文件存在"] = True
|
||||
|
||||
with open(project_repo_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
if "get_project_file_paths" in content:
|
||||
checklist["ProjectRepository"]["文件路径管理"] = True
|
||||
|
||||
return checklist
|
||||
|
||||
def check_api_endpoints():
|
||||
"""检查API端点优化状态"""
|
||||
print("\n🔍 检查API端点优化状态...")
|
||||
|
||||
checklist = {
|
||||
"文件上传API": {
|
||||
"文件存在": False,
|
||||
"优化存储逻辑": False
|
||||
},
|
||||
"切片API": {
|
||||
"文件存在": False,
|
||||
"按需加载数据": False
|
||||
},
|
||||
"合集API": {
|
||||
"文件存在": False,
|
||||
"按需加载数据": False
|
||||
},
|
||||
"文件访问API": {
|
||||
"文件存在": False,
|
||||
"内容访问端点": False
|
||||
}
|
||||
}
|
||||
|
||||
# 检查文件上传API
|
||||
files_api_path = backend_dir / "api" / "v1" / "files.py"
|
||||
if files_api_path.exists():
|
||||
checklist["文件上传API"]["文件存在"] = True
|
||||
|
||||
with open(files_api_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
if "upload" in content:
|
||||
checklist["文件上传API"]["优化存储逻辑"] = True
|
||||
|
||||
# 检查切片API
|
||||
clips_api_path = backend_dir / "api" / "v1" / "clips.py"
|
||||
if clips_api_path.exists():
|
||||
checklist["切片API"]["文件存在"] = True
|
||||
|
||||
with open(clips_api_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
if "get_clips" in content:
|
||||
checklist["切片API"]["按需加载数据"] = True
|
||||
|
||||
# 检查合集API
|
||||
collections_api_path = backend_dir / "api" / "v1" / "collections.py"
|
||||
if collections_api_path.exists():
|
||||
checklist["合集API"]["文件存在"] = True
|
||||
|
||||
with open(collections_api_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
if "include_content" in content:
|
||||
checklist["合集API"]["按需加载数据"] = True
|
||||
|
||||
# 检查文件访问API
|
||||
files_api_path = backend_dir / "api" / "v1" / "files.py"
|
||||
if files_api_path.exists():
|
||||
checklist["文件访问API"]["文件存在"] = True
|
||||
|
||||
with open(files_api_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
if "get_clip_content" in content:
|
||||
checklist["文件访问API"]["内容访问端点"] = True
|
||||
|
||||
return checklist
|
||||
|
||||
def check_migration_scripts():
|
||||
"""检查数据迁移脚本状态"""
|
||||
print("\n🔍 检查数据迁移脚本状态...")
|
||||
|
||||
checklist = {
|
||||
"迁移脚本": {
|
||||
"文件存在": False,
|
||||
"数据验证": False,
|
||||
"回滚机制": False
|
||||
}
|
||||
}
|
||||
|
||||
migration_script_path = backend_dir / "migrations" / "optimize_storage_models.py"
|
||||
|
||||
if migration_script_path.exists():
|
||||
checklist["迁移脚本"]["文件存在"] = True
|
||||
|
||||
with open(migration_script_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
if "validate_migration" in content:
|
||||
checklist["迁移脚本"]["数据验证"] = True
|
||||
|
||||
if "rollback_migration" in content:
|
||||
checklist["迁移脚本"]["回滚机制"] = True
|
||||
|
||||
return checklist
|
||||
|
||||
def check_file_structure():
|
||||
"""检查文件结构优化状态"""
|
||||
print("\n🔍 检查文件结构优化状态...")
|
||||
|
||||
checklist = {
|
||||
"目录结构": {
|
||||
"temp目录": False,
|
||||
"cache目录": False,
|
||||
"backups目录": False,
|
||||
"示例项目结构": False
|
||||
}
|
||||
}
|
||||
|
||||
data_dir = project_root / "data"
|
||||
|
||||
if (data_dir / "temp").exists():
|
||||
checklist["目录结构"]["temp目录"] = True
|
||||
|
||||
if (data_dir / "cache").exists():
|
||||
checklist["目录结构"]["cache目录"] = True
|
||||
|
||||
if (data_dir / "backups").exists():
|
||||
checklist["目录结构"]["backups目录"] = True
|
||||
|
||||
if (data_dir / "projects" / "example-project").exists():
|
||||
checklist["目录结构"]["示例项目结构"] = True
|
||||
|
||||
return checklist
|
||||
|
||||
def print_checklist_results(all_checklists: Dict[str, Dict[str, Dict[str, bool]]]):
|
||||
"""打印检查清单结果"""
|
||||
print("\n" + "="*60)
|
||||
print("📋 存储优化实施检查清单结果")
|
||||
print("="*60)
|
||||
|
||||
total_items = 0
|
||||
completed_items = 0
|
||||
|
||||
for category, items in all_checklists.items():
|
||||
print(f"\n🔸 {category}")
|
||||
print("-" * 40)
|
||||
|
||||
for subcategory, checks in items.items():
|
||||
print(f" 📁 {subcategory}")
|
||||
|
||||
for check_name, completed in checks.items():
|
||||
status = "✅" if completed else "❌"
|
||||
print(f" {status} {check_name}")
|
||||
total_items += 1
|
||||
if completed:
|
||||
completed_items += 1
|
||||
|
||||
print("\n" + "="*60)
|
||||
completion_rate = (completed_items / total_items * 100) if total_items > 0 else 0
|
||||
print(f"📊 总体完成度: {completion_rate:.1f}% ({completed_items}/{total_items})")
|
||||
|
||||
if completion_rate >= 80:
|
||||
print("🎉 存储优化实施进展良好!")
|
||||
elif completion_rate >= 50:
|
||||
print("🚧 存储优化实施进行中,需要继续推进")
|
||||
else:
|
||||
print("⚠️ 存储优化实施需要加快进度")
|
||||
|
||||
print("="*60)
|
||||
|
||||
def generate_next_steps(all_checklists: Dict[str, Dict[str, Dict[str, bool]]]):
|
||||
"""生成下一步行动计划"""
|
||||
print("\n📝 下一步行动计划:")
|
||||
print("-" * 40)
|
||||
|
||||
next_steps = []
|
||||
|
||||
# 检查数据库模型优化
|
||||
db_models = all_checklists.get("数据库模型优化", {})
|
||||
for subcategory, checks in db_models.items():
|
||||
for check_name, completed in checks.items():
|
||||
if not completed:
|
||||
next_steps.append(f"🔧 完成 {subcategory} - {check_name}")
|
||||
|
||||
# 检查存储服务
|
||||
storage_service = all_checklists.get("存储服务", {})
|
||||
for subcategory, checks in storage_service.items():
|
||||
for check_name, completed in checks.items():
|
||||
if not completed:
|
||||
next_steps.append(f"🔧 完成 {subcategory} - {check_name}")
|
||||
|
||||
# 检查PipelineAdapter
|
||||
pipeline_adapter = all_checklists.get("PipelineAdapter", {})
|
||||
for subcategory, checks in pipeline_adapter.items():
|
||||
for check_name, completed in checks.items():
|
||||
if not completed:
|
||||
next_steps.append(f"🔧 完成 {subcategory} - {check_name}")
|
||||
|
||||
# 检查Repository层
|
||||
repositories = all_checklists.get("Repository层", {})
|
||||
for subcategory, checks in repositories.items():
|
||||
for check_name, completed in checks.items():
|
||||
if not completed:
|
||||
next_steps.append(f"🔧 完成 {subcategory} - {check_name}")
|
||||
|
||||
# 检查API端点
|
||||
api_endpoints = all_checklists.get("API端点", {})
|
||||
for subcategory, checks in api_endpoints.items():
|
||||
for check_name, completed in checks.items():
|
||||
if not completed:
|
||||
next_steps.append(f"🔧 完成 {subcategory} - {check_name}")
|
||||
|
||||
# 检查迁移脚本
|
||||
migration_scripts = all_checklists.get("迁移脚本", {})
|
||||
for subcategory, checks in migration_scripts.items():
|
||||
for check_name, completed in checks.items():
|
||||
if not completed:
|
||||
next_steps.append(f"🔧 完成 {subcategory} - {check_name}")
|
||||
|
||||
# 检查文件结构
|
||||
file_structure = all_checklists.get("文件结构", {})
|
||||
for subcategory, checks in file_structure.items():
|
||||
for check_name, completed in checks.items():
|
||||
if not completed:
|
||||
next_steps.append(f"🔧 完成 {subcategory} - {check_name}")
|
||||
|
||||
if next_steps:
|
||||
for i, step in enumerate(next_steps[:10], 1): # 只显示前10个
|
||||
print(f"{i}. {step}")
|
||||
|
||||
if len(next_steps) > 10:
|
||||
print(f"... 还有 {len(next_steps) - 10} 个任务")
|
||||
else:
|
||||
print("🎉 所有任务已完成!")
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("🚀 开始存储优化实施检查...")
|
||||
|
||||
# 执行各项检查
|
||||
all_checklists = {
|
||||
"数据库模型优化": check_database_models(),
|
||||
"存储服务": check_storage_service(),
|
||||
"PipelineAdapter": check_pipeline_adapter(),
|
||||
"Repository层": check_repositories(),
|
||||
"API端点": check_api_endpoints(),
|
||||
"迁移脚本": check_migration_scripts(),
|
||||
"文件结构": check_file_structure()
|
||||
}
|
||||
|
||||
# 打印结果
|
||||
print_checklist_results(all_checklists)
|
||||
|
||||
# 生成下一步计划
|
||||
generate_next_steps(all_checklists)
|
||||
|
||||
print("\n📚 相关文档:")
|
||||
print("- docs/STORAGE_ARCHITECTURE_OPTIMIZATION.md")
|
||||
print("- docs/STORAGE_OPTIMIZATION_WORK_BREAKDOWN.md")
|
||||
print("- docs/STORAGE_ARCHITECTURE_ANALYSIS.md")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,133 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
清理数据库中的重复切片数据
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
from core.database import SessionLocal
|
||||
from models.clip import Clip
|
||||
from models.project import Project
|
||||
import json
|
||||
import logging
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def cleanup_duplicate_clips():
|
||||
"""清理重复的切片数据"""
|
||||
db = SessionLocal()
|
||||
|
||||
try:
|
||||
# 获取所有项目
|
||||
projects = db.query(Project).all()
|
||||
|
||||
for project in projects:
|
||||
logger.info(f"处理项目: {project.name} (ID: {project.id})")
|
||||
|
||||
# 获取项目的所有切片
|
||||
clips = db.query(Clip).filter(Clip.project_id == project.id).all()
|
||||
logger.info(f" 数据库中有 {len(clips)} 个切片")
|
||||
|
||||
# 读取文件系统中的原始数据
|
||||
data_dir = Path("data/projects")
|
||||
project_dir = data_dir / project.id
|
||||
|
||||
clips_metadata_file = project_dir / "clips_metadata.json"
|
||||
if not clips_metadata_file.exists():
|
||||
logger.warning(f" 项目 {project.id} 的clips_metadata.json不存在")
|
||||
continue
|
||||
|
||||
try:
|
||||
with open(clips_metadata_file, 'r', encoding='utf-8') as f:
|
||||
original_clips = json.load(f)
|
||||
logger.info(f" 文件系统中有 {len(original_clips)} 个切片")
|
||||
|
||||
# 创建原始切片的ID映射
|
||||
original_clip_ids = {clip['id']: clip for clip in original_clips}
|
||||
|
||||
# 删除数据库中的重复切片,只保留与文件系统匹配的切片
|
||||
deleted_count = 0
|
||||
kept_count = 0
|
||||
|
||||
for db_clip in clips:
|
||||
# 检查这个切片是否在原始数据中
|
||||
metadata = db_clip.clip_metadata or {}
|
||||
original_id = metadata.get('id')
|
||||
|
||||
if original_id and original_id in original_clip_ids:
|
||||
# 这个切片是有效的,保留
|
||||
kept_count += 1
|
||||
logger.info(f" 保留切片: {db_clip.title} (ID: {original_id})")
|
||||
else:
|
||||
# 这个切片是重复的或无效的,删除
|
||||
logger.info(f" 删除重复切片: {db_clip.title} (DB ID: {db_clip.id})")
|
||||
db.delete(db_clip)
|
||||
deleted_count += 1
|
||||
|
||||
db.commit()
|
||||
logger.info(f" 项目 {project.id} 清理完成: 保留 {kept_count} 个,删除 {deleted_count} 个")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f" 处理项目 {project.id} 时出错: {e}")
|
||||
db.rollback()
|
||||
|
||||
logger.info("所有项目清理完成")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"清理过程中出错: {e}")
|
||||
db.rollback()
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
def verify_clips_data():
|
||||
"""验证切片数据的正确性"""
|
||||
db = SessionLocal()
|
||||
|
||||
try:
|
||||
projects = db.query(Project).all()
|
||||
|
||||
for project in projects:
|
||||
logger.info(f"\n验证项目: {project.name}")
|
||||
|
||||
clips = db.query(Clip).filter(Clip.project_id == project.id).all()
|
||||
logger.info(f" 数据库切片数量: {len(clips)}")
|
||||
|
||||
# 检查时间数据
|
||||
for clip in clips:
|
||||
if clip.start_time == 0 and clip.end_time > 0:
|
||||
logger.warning(f" 切片 {clip.title} 的start_time为0,可能有问题")
|
||||
|
||||
if clip.duration <= 0:
|
||||
logger.warning(f" 切片 {clip.title} 的duration为{clip.duration},可能有问题")
|
||||
|
||||
# 检查metadata
|
||||
metadata = clip.clip_metadata or {}
|
||||
if not metadata.get('id'):
|
||||
logger.warning(f" 切片 {clip.title} 缺少原始ID")
|
||||
|
||||
# 检查content字段
|
||||
content = metadata.get('content', [])
|
||||
if not content or (isinstance(content, list) and len(content) == 0):
|
||||
logger.warning(f" 切片 {clip.title} 缺少content数据")
|
||||
elif isinstance(content, list) and len(content) == 1 and content[0] == clip.title:
|
||||
logger.warning(f" 切片 {clip.title} 的content只是标题的重复")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"验证过程中出错: {e}")
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("🧹 开始清理重复的切片数据...")
|
||||
cleanup_duplicate_clips()
|
||||
|
||||
print("\n🔍 验证切片数据...")
|
||||
verify_clips_data()
|
||||
|
||||
print("\n✅ 清理和验证完成!")
|
||||
@@ -1,126 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
专门诊断前端拖拽排序问题
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
import time
|
||||
|
||||
def debug_frontend_issue():
|
||||
"""诊断前端拖拽排序问题"""
|
||||
|
||||
# 项目ID和合集ID
|
||||
project_id = "86f9aa12-2f35-4618-b265-74b3d9a4cf2d"
|
||||
collection_id = "5e5dafc8-f29a-4705-8e87-b2bb06f2a5de"
|
||||
|
||||
print("🔍 专门诊断前端拖拽排序问题")
|
||||
print("=" * 60)
|
||||
|
||||
# 1. 检查后端服务状态
|
||||
print("1️⃣ 检查后端服务状态...")
|
||||
try:
|
||||
response = requests.get("http://localhost:8000/api/v1/")
|
||||
print(f"✅ 后端服务正常运行")
|
||||
except Exception as e:
|
||||
print(f"❌ 后端服务异常: {e}")
|
||||
return
|
||||
|
||||
# 2. 检查当前数据状态
|
||||
print("\n2️⃣ 检查当前数据状态...")
|
||||
try:
|
||||
response = requests.get(f"http://localhost:8000/api/v1/collections/?project_id={project_id}")
|
||||
if response.status_code == 200:
|
||||
collections = response.json()['items']
|
||||
target_collection = next((c for c in collections if c['id'] == collection_id), None)
|
||||
if target_collection:
|
||||
current_clip_ids = target_collection['clip_ids']
|
||||
print(f"✅ 目标合集: {target_collection['name']}")
|
||||
print(f"📋 当前clip_ids: {current_clip_ids}")
|
||||
print(f"📊 片段数量: {len(current_clip_ids)}")
|
||||
else:
|
||||
print("❌ 未找到目标合集")
|
||||
return
|
||||
else:
|
||||
print(f"❌ 获取collections失败: {response.status_code}")
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"❌ 获取数据异常: {e}")
|
||||
return
|
||||
|
||||
# 3. 监控拖拽排序调用
|
||||
print("\n3️⃣ 监控拖拽排序调用...")
|
||||
print("🔔 请在前端进行拖拽排序操作,然后按回车键继续...")
|
||||
input()
|
||||
|
||||
# 检查最近的日志
|
||||
print("📋 检查后端日志中的拖拽排序记录...")
|
||||
try:
|
||||
with open('/Users/zhoukk/autoclip/backend.log', 'r') as f:
|
||||
lines = f.readlines()
|
||||
recent_lines = lines[-50:] # 获取最近50行
|
||||
|
||||
reorder_logs = []
|
||||
for line in recent_lines:
|
||||
if 'reorder' in line.lower() or '排序' in line:
|
||||
reorder_logs.append(line.strip())
|
||||
|
||||
if reorder_logs:
|
||||
print("📋 找到相关日志:")
|
||||
for log in reorder_logs[-10:]: # 显示最近10条
|
||||
print(f" {log}")
|
||||
else:
|
||||
print("❌ 未找到拖拽排序相关的日志")
|
||||
except Exception as e:
|
||||
print(f"❌ 读取日志失败: {e}")
|
||||
|
||||
# 4. 再次检查数据状态
|
||||
print("\n4️⃣ 再次检查数据状态...")
|
||||
try:
|
||||
response = requests.get(f"http://localhost:8000/api/v1/collections/?project_id={project_id}")
|
||||
if response.status_code == 200:
|
||||
collections = response.json()['items']
|
||||
target_collection = next((c for c in collections if c['id'] == collection_id), None)
|
||||
if target_collection:
|
||||
updated_clip_ids = target_collection['clip_ids']
|
||||
print(f"📋 更新后的clip_ids: {updated_clip_ids}")
|
||||
|
||||
if updated_clip_ids != current_clip_ids:
|
||||
print("✅ 数据已更新!")
|
||||
else:
|
||||
print("❌ 数据未更新")
|
||||
else:
|
||||
print("❌ 未找到目标合集")
|
||||
else:
|
||||
print(f"❌ 获取collections失败: {response.status_code}")
|
||||
except Exception as e:
|
||||
print(f"❌ 获取数据异常: {e}")
|
||||
|
||||
# 5. 提供调试建议
|
||||
print("\n5️⃣ 调试建议...")
|
||||
print("📋 如果拖拽排序仍然失败,请检查以下几点:")
|
||||
print(" 1. 打开浏览器开发者工具的Network标签")
|
||||
print(" 2. 进行拖拽排序操作")
|
||||
print(" 3. 查看是否有API调用发出")
|
||||
print(" 4. 检查API调用的状态码和响应")
|
||||
print(" 5. 查看Console标签是否有JavaScript错误")
|
||||
|
||||
print("\n6️⃣ 常见问题排查:")
|
||||
print(" ❓ 问题1: 前端没有发出API请求")
|
||||
print(" 💡 解决: 检查前端代码中的事件处理是否正确绑定")
|
||||
print()
|
||||
print(" ❓ 问题2: API请求被拦截或失败")
|
||||
print(" 💡 解决: 检查CORS设置,确认后端服务可访问")
|
||||
print()
|
||||
print(" ❓ 问题3: 前端状态更新失败")
|
||||
print(" 💡 解决: 检查store的状态管理逻辑")
|
||||
print()
|
||||
print(" ❓ 问题4: 错误处理逻辑问题")
|
||||
print(" 💡 解决: 检查try-catch块和错误消息显示")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("🎯 诊断完成!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
debug_frontend_issue()
|
||||
|
||||
@@ -1,237 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
调试前端可能遇到的所有问题
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
from typing import List
|
||||
|
||||
def debug_frontend_issues():
|
||||
"""调试前端可能遇到的所有问题"""
|
||||
|
||||
# 测试数据
|
||||
project_id = "5c48803d-0aa7-48d7-a270-2b33e4954f25"
|
||||
collection_id = "0e181e1a-52c2-42c2-9481-cc306e3b27f9"
|
||||
|
||||
print("🔍 调试前端可能遇到的所有问题")
|
||||
print("=" * 60)
|
||||
|
||||
# 1. 检查后端服务是否正常运行
|
||||
print("\n1️⃣ 检查后端服务状态...")
|
||||
try:
|
||||
response = requests.get("http://localhost:8000/health")
|
||||
print(f"📥 健康检查响应: {response.status_code}")
|
||||
if response.status_code == 200:
|
||||
print("✅ 后端服务正常运行")
|
||||
else:
|
||||
print("❌ 后端服务异常")
|
||||
except Exception as e:
|
||||
print(f"❌ 后端服务连接失败: {e}")
|
||||
return
|
||||
|
||||
# 2. 检查项目是否存在
|
||||
print("\n2️⃣ 检查项目是否存在...")
|
||||
try:
|
||||
response = requests.get(f"http://localhost:8000/api/v1/projects/{project_id}")
|
||||
print(f"📥 项目查询响应: {response.status_code}")
|
||||
if response.status_code == 200:
|
||||
project = response.json()
|
||||
print(f"✅ 项目存在: {project.get('name', 'Unknown')}")
|
||||
else:
|
||||
print(f"❌ 项目不存在: {response.text}")
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"❌ 项目查询异常: {e}")
|
||||
return
|
||||
|
||||
# 3. 检查合集是否存在
|
||||
print("\n3️⃣ 检查合集是否存在...")
|
||||
try:
|
||||
response = requests.get(f"http://localhost:8000/api/v1/collections/{collection_id}")
|
||||
print(f"📥 合集查询响应: {response.status_code}")
|
||||
if response.status_code == 200:
|
||||
collection = response.json()
|
||||
print(f"✅ 合集存在: {collection.get('name', 'Unknown')}")
|
||||
current_clip_ids = collection.get('clip_ids', [])
|
||||
print(f"📋 当前clip_ids: {current_clip_ids}")
|
||||
else:
|
||||
print(f"❌ 合集不存在: {response.text}")
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"❌ 合集查询异常: {e}")
|
||||
return
|
||||
|
||||
if len(current_clip_ids) < 2:
|
||||
print("⚠️ clip_ids数量不足,无法测试排序")
|
||||
return
|
||||
|
||||
# 4. 测试前端可能使用的所有API端点
|
||||
print("\n4️⃣ 测试前端可能使用的所有API端点...")
|
||||
|
||||
# 端点1: 新的排序端点
|
||||
print("\n🔄 端点1: PATCH /collections/{collection_id}/reorder")
|
||||
new_clip_ids = current_clip_ids[1:] + current_clip_ids[:1]
|
||||
try:
|
||||
response = requests.patch(
|
||||
f"http://localhost:8000/api/v1/collections/{collection_id}/reorder",
|
||||
json=new_clip_ids,
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
print(f"📥 响应状态: {response.status_code}")
|
||||
print(f"📥 响应头: {dict(response.headers)}")
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
print(f"✅ 端点1成功: {result.get('clip_ids', [])}")
|
||||
else:
|
||||
print(f"❌ 端点1失败: {response.text}")
|
||||
except Exception as e:
|
||||
print(f"❌ 端点1异常: {e}")
|
||||
|
||||
# 端点2: 旧的PUT端点
|
||||
print("\n🔄 端点2: PUT /collections/{collection_id}")
|
||||
try:
|
||||
update_data = {
|
||||
"metadata": {
|
||||
"clip_ids": new_clip_ids
|
||||
}
|
||||
}
|
||||
response = requests.put(
|
||||
f"http://localhost:8000/api/v1/collections/{collection_id}",
|
||||
json=update_data,
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
print(f"📥 响应状态: {response.status_code}")
|
||||
print(f"📥 响应头: {dict(response.headers)}")
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
print(f"✅ 端点2成功: {result.get('clip_ids', [])}")
|
||||
else:
|
||||
print(f"❌ 端点2失败: {response.text}")
|
||||
except Exception as e:
|
||||
print(f"❌ 端点2异常: {e}")
|
||||
|
||||
# 端点3: 检查是否有其他端点
|
||||
print("\n🔄 端点3: 检查其他可能的端点")
|
||||
possible_endpoints = [
|
||||
f"/api/v1/projects/{project_id}/collections/{collection_id}/reorder",
|
||||
f"/api/v1/collections/{collection_id}/clips/reorder",
|
||||
f"/api/v1/projects/{project_id}/collections/{collection_id}/clips/reorder"
|
||||
]
|
||||
|
||||
for endpoint in possible_endpoints:
|
||||
try:
|
||||
response = requests.patch(
|
||||
f"http://localhost:8000{endpoint}",
|
||||
json=new_clip_ids,
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
print(f"📥 {endpoint} - 响应状态: {response.status_code}")
|
||||
if response.status_code == 200:
|
||||
print(f"✅ {endpoint} 存在且工作正常")
|
||||
elif response.status_code == 404:
|
||||
print(f"❌ {endpoint} 不存在")
|
||||
else:
|
||||
print(f"⚠️ {endpoint} 返回: {response.status_code}")
|
||||
except Exception as e:
|
||||
print(f"❌ {endpoint} 异常: {e}")
|
||||
|
||||
# 5. 检查CORS设置
|
||||
print("\n5️⃣ 检查CORS设置...")
|
||||
try:
|
||||
response = requests.options(
|
||||
f"http://localhost:8000/api/v1/collections/{collection_id}/reorder",
|
||||
headers={
|
||||
"Origin": "http://localhost:3000",
|
||||
"Access-Control-Request-Method": "PATCH",
|
||||
"Access-Control-Request-Headers": "Content-Type"
|
||||
}
|
||||
)
|
||||
print(f"📥 CORS预检响应: {response.status_code}")
|
||||
print(f"📥 CORS响应头: {dict(response.headers)}")
|
||||
if response.status_code == 200:
|
||||
print("✅ CORS设置正常")
|
||||
else:
|
||||
print("❌ CORS设置异常")
|
||||
except Exception as e:
|
||||
print(f"❌ CORS检查异常: {e}")
|
||||
|
||||
# 6. 检查前端可能发送的错误请求格式
|
||||
print("\n6️⃣ 检查前端可能发送的错误请求格式...")
|
||||
|
||||
# 格式1: 错误的Content-Type
|
||||
print("\n🔄 格式1: 错误的Content-Type")
|
||||
try:
|
||||
response = requests.patch(
|
||||
f"http://localhost:8000/api/v1/collections/{collection_id}/reorder",
|
||||
data=json.dumps(new_clip_ids),
|
||||
headers={"Content-Type": "text/plain"}
|
||||
)
|
||||
print(f"📥 响应状态: {response.status_code}")
|
||||
if response.status_code == 200:
|
||||
print("✅ 格式1成功")
|
||||
else:
|
||||
print(f"❌ 格式1失败: {response.text}")
|
||||
except Exception as e:
|
||||
print(f"❌ 格式1异常: {e}")
|
||||
|
||||
# 格式2: 错误的JSON格式
|
||||
print("\n🔄 格式2: 错误的JSON格式")
|
||||
try:
|
||||
response = requests.patch(
|
||||
f"http://localhost:8000/api/v1/collections/{collection_id}/reorder",
|
||||
json={"clip_ids": new_clip_ids},
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
print(f"📥 响应状态: {response.status_code}")
|
||||
if response.status_code == 200:
|
||||
print("✅ 格式2成功")
|
||||
else:
|
||||
print(f"❌ 格式2失败: {response.text}")
|
||||
except Exception as e:
|
||||
print(f"❌ 格式2异常: {e}")
|
||||
|
||||
# 格式3: 空数据
|
||||
print("\n🔄 格式3: 空数据")
|
||||
try:
|
||||
response = requests.patch(
|
||||
f"http://localhost:8000/api/v1/collections/{collection_id}/reorder",
|
||||
json=[],
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
print(f"📥 响应状态: {response.status_code}")
|
||||
if response.status_code == 200:
|
||||
print("✅ 格式3成功")
|
||||
else:
|
||||
print(f"❌ 格式3失败: {response.text}")
|
||||
except Exception as e:
|
||||
print(f"❌ 格式3异常: {e}")
|
||||
|
||||
# 7. 检查数据库状态
|
||||
print("\n7️⃣ 检查数据库状态...")
|
||||
try:
|
||||
response = requests.get(f"http://localhost:8000/api/v1/collections/{collection_id}")
|
||||
if response.status_code == 200:
|
||||
collection = response.json()
|
||||
updated_clip_ids = collection.get('clip_ids', [])
|
||||
print(f"✅ 数据库状态: {updated_clip_ids}")
|
||||
|
||||
if updated_clip_ids == new_clip_ids:
|
||||
print("✅ 数据库更新成功")
|
||||
else:
|
||||
print("❌ 数据库更新失败")
|
||||
else:
|
||||
print(f"❌ 数据库状态检查失败: {response.status_code}")
|
||||
except Exception as e:
|
||||
print(f"❌ 数据库状态检查异常: {e}")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("🎉 前端问题调试完成!")
|
||||
print("\n💡 建议:")
|
||||
print("1. 检查前端控制台是否有JavaScript错误")
|
||||
print("2. 检查网络面板中的请求详情")
|
||||
print("3. 确认前端使用的是最新版本的代码")
|
||||
print("4. 尝试清除浏览器缓存")
|
||||
|
||||
if __name__ == "__main__":
|
||||
debug_frontend_issues()
|
||||
@@ -1,179 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
调试前端拖拽排序问题
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
|
||||
def debug_frontend_reorder():
|
||||
"""调试前端拖拽排序问题"""
|
||||
|
||||
# 项目ID和合集ID
|
||||
project_id = "86f9aa12-2f35-4618-b265-74b3d9a4cf2d"
|
||||
collection_id = "5e5dafc8-f29a-4705-8e87-b2bb06f2a5de"
|
||||
|
||||
print("🔍 调试前端拖拽排序问题")
|
||||
print("=" * 50)
|
||||
|
||||
# 1. 检查后端API是否正常工作
|
||||
print("1️⃣ 检查后端API...")
|
||||
try:
|
||||
response = requests.get(f"http://localhost:8000/api/v1/collections/?project_id={project_id}")
|
||||
if response.status_code == 200:
|
||||
collections = response.json()['items']
|
||||
target_collection = next((c for c in collections if c['id'] == collection_id), None)
|
||||
if target_collection:
|
||||
initial_clip_ids = target_collection['clip_ids']
|
||||
print(f"✅ 后端API正常,合集: {target_collection['name']}")
|
||||
print(f"📋 当前clip_ids: {initial_clip_ids}")
|
||||
else:
|
||||
print("❌ 未找到目标合集")
|
||||
return
|
||||
else:
|
||||
print(f"❌ 后端API异常: {response.status_code}")
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"❌ 后端API异常: {e}")
|
||||
return
|
||||
|
||||
# 2. 测试不同的API调用方式
|
||||
print("\n2️⃣ 测试不同的API调用方式...")
|
||||
|
||||
# 方式1: 使用正确的API端点
|
||||
print("\n🔄 方式1: PATCH /projects/{project_id}/collections/{collection_id}/reorder")
|
||||
new_clip_ids_1 = [initial_clip_ids[1], initial_clip_ids[0]] + initial_clip_ids[2:]
|
||||
try:
|
||||
response = requests.patch(
|
||||
f"http://localhost:8000/api/v1/projects/{project_id}/collections/{collection_id}/reorder",
|
||||
json=new_clip_ids_1,
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
print(f"📥 响应状态: {response.status_code}")
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
print(f"✅ 方式1成功: {result.get('clip_ids', [])}")
|
||||
else:
|
||||
print(f"❌ 方式1失败: {response.text}")
|
||||
except Exception as e:
|
||||
print(f"❌ 方式1异常: {e}")
|
||||
|
||||
# 方式2: 使用错误的API端点(前端可能使用的)
|
||||
print("\n🔄 方式2: PATCH /collections/{collection_id}/reorder")
|
||||
new_clip_ids_2 = [initial_clip_ids[0], initial_clip_ids[1]] + initial_clip_ids[2:]
|
||||
try:
|
||||
response = requests.patch(
|
||||
f"http://localhost:8000/api/v1/collections/{collection_id}/reorder",
|
||||
json=new_clip_ids_2,
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
print(f"📥 响应状态: {response.status_code}")
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
print(f"✅ 方式2成功: {result.get('clip_ids', [])}")
|
||||
else:
|
||||
print(f"❌ 方式2失败: {response.text}")
|
||||
except Exception as e:
|
||||
print(f"❌ 方式2异常: {e}")
|
||||
|
||||
# 方式3: 使用PUT方式更新metadata
|
||||
print("\n🔄 方式3: PUT /collections/{collection_id} (metadata格式)")
|
||||
new_clip_ids_3 = [initial_clip_ids[1], initial_clip_ids[0]] + initial_clip_ids[2:]
|
||||
try:
|
||||
update_data = {
|
||||
"metadata": {
|
||||
"clip_ids": new_clip_ids_3
|
||||
}
|
||||
}
|
||||
response = requests.put(
|
||||
f"http://localhost:8000/api/v1/collections/{collection_id}",
|
||||
json=update_data,
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
print(f"📥 响应状态: {response.status_code}")
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
print(f"✅ 方式3成功: {result.get('clip_ids', [])}")
|
||||
else:
|
||||
print(f"❌ 方式3失败: {response.text}")
|
||||
except Exception as e:
|
||||
print(f"❌ 方式3异常: {e}")
|
||||
|
||||
# 3. 检查前端可能的问题
|
||||
print("\n3️⃣ 检查前端可能的问题...")
|
||||
|
||||
# 检查网络连接
|
||||
print("\n🌐 检查网络连接...")
|
||||
try:
|
||||
response = requests.get("http://localhost:8000/api/v1/")
|
||||
print(f"✅ 网络连接正常: {response.status_code}")
|
||||
except Exception as e:
|
||||
print(f"❌ 网络连接异常: {e}")
|
||||
|
||||
# 检查CORS
|
||||
print("\n🔒 检查CORS...")
|
||||
try:
|
||||
response = requests.options(
|
||||
f"http://localhost:8000/api/v1/projects/{project_id}/collections/{collection_id}/reorder",
|
||||
headers={"Origin": "http://localhost:3000"}
|
||||
)
|
||||
print(f"✅ CORS检查: {response.status_code}")
|
||||
print(f"📋 CORS头: {dict(response.headers)}")
|
||||
except Exception as e:
|
||||
print(f"❌ CORS检查异常: {e}")
|
||||
|
||||
# 4. 模拟前端的完整错误流程
|
||||
print("\n4️⃣ 模拟前端的完整错误流程...")
|
||||
|
||||
# 模拟前端可能遇到的错误
|
||||
print("\n🔄 模拟错误1: 无效的collection_id")
|
||||
try:
|
||||
response = requests.patch(
|
||||
f"http://localhost:8000/api/v1/projects/{project_id}/collections/invalid-id/reorder",
|
||||
json=initial_clip_ids,
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
print(f"📥 错误1响应: {response.status_code}")
|
||||
if response.status_code == 404:
|
||||
print("✅ 错误处理正常")
|
||||
else:
|
||||
print(f"❌ 错误处理异常: {response.text}")
|
||||
except Exception as e:
|
||||
print(f"❌ 错误1异常: {e}")
|
||||
|
||||
print("\n🔄 模拟错误2: 无效的请求体")
|
||||
try:
|
||||
response = requests.patch(
|
||||
f"http://localhost:8000/api/v1/projects/{project_id}/collections/{collection_id}/reorder",
|
||||
json={"invalid": "data"},
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
print(f"📥 错误2响应: {response.status_code}")
|
||||
print(f"📋 错误2内容: {response.text}")
|
||||
except Exception as e:
|
||||
print(f"❌ 错误2异常: {e}")
|
||||
|
||||
print("\n🔄 模拟错误3: 空的clip_ids")
|
||||
try:
|
||||
response = requests.patch(
|
||||
f"http://localhost:8000/api/v1/projects/{project_id}/collections/{collection_id}/reorder",
|
||||
json=[],
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
print(f"📥 错误3响应: {response.status_code}")
|
||||
print(f"📋 错误3内容: {response.text}")
|
||||
except Exception as e:
|
||||
print(f"❌ 错误3异常: {e}")
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("🎯 调试完成!")
|
||||
print("\n📋 可能的问题:")
|
||||
print(" 1. 前端使用了错误的API端点")
|
||||
print(" 2. 前端发送了错误的请求格式")
|
||||
print(" 3. 网络连接问题")
|
||||
print(" 4. CORS问题")
|
||||
print(" 5. 前端错误处理逻辑问题")
|
||||
|
||||
if __name__ == "__main__":
|
||||
debug_frontend_reorder()
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
调试前端实际发送的请求
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
from typing import List
|
||||
|
||||
def debug_frontend_request():
|
||||
"""调试前端实际发送的请求"""
|
||||
|
||||
# 测试数据
|
||||
project_id = "5c48803d-0aa7-48d7-a270-2b33e4954f25"
|
||||
collection_id = "0e181e1a-52c2-42c2-9481-cc306e3b27f9"
|
||||
|
||||
print("🔍 调试前端实际发送的请求")
|
||||
print("=" * 50)
|
||||
|
||||
# 1. 获取当前合集信息
|
||||
print("\n1️⃣ 获取当前合集信息...")
|
||||
try:
|
||||
response = requests.get(f"http://localhost:8000/api/v1/collections/{collection_id}")
|
||||
if response.status_code == 200:
|
||||
collection = response.json()
|
||||
print(f"✅ 当前合集: {collection['name']}")
|
||||
current_clip_ids = collection.get('clip_ids', [])
|
||||
print(f"📋 当前clip_ids: {current_clip_ids}")
|
||||
else:
|
||||
print(f"❌ 获取合集失败: {response.status_code}")
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"❌ 获取合集异常: {e}")
|
||||
return
|
||||
|
||||
if len(current_clip_ids) < 2:
|
||||
print("⚠️ clip_ids数量不足,无法测试排序")
|
||||
return
|
||||
|
||||
# 2. 测试前端可能使用的不同API调用方式
|
||||
print("\n2️⃣ 测试前端可能使用的不同API调用方式...")
|
||||
|
||||
# 方式1: 新的排序端点
|
||||
print("\n🔄 方式1: PATCH /collections/{collection_id}/reorder")
|
||||
new_clip_ids = current_clip_ids[1:] + current_clip_ids[:1]
|
||||
try:
|
||||
response = requests.patch(
|
||||
f"http://localhost:8000/api/v1/collections/{collection_id}/reorder",
|
||||
json=new_clip_ids,
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
print(f"📥 响应状态: {response.status_code}")
|
||||
if response.status_code == 200:
|
||||
print("✅ 方式1成功")
|
||||
else:
|
||||
print(f"❌ 方式1失败: {response.text}")
|
||||
except Exception as e:
|
||||
print(f"❌ 方式1异常: {e}")
|
||||
|
||||
# 方式2: 旧的PUT方式
|
||||
print("\n🔄 方式2: PUT /collections/{collection_id}")
|
||||
try:
|
||||
update_data = {
|
||||
"metadata": {
|
||||
"clip_ids": new_clip_ids
|
||||
}
|
||||
}
|
||||
response = requests.put(
|
||||
f"http://localhost:8000/api/v1/collections/{collection_id}",
|
||||
json=update_data,
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
print(f"📥 响应状态: {response.status_code}")
|
||||
if response.status_code == 200:
|
||||
print("✅ 方式2成功")
|
||||
else:
|
||||
print(f"❌ 方式2失败: {response.text}")
|
||||
except Exception as e:
|
||||
print(f"❌ 方式2异常: {e}")
|
||||
|
||||
# 方式3: 前端可能使用的其他格式
|
||||
print("\n🔄 方式3: PUT /collections/{collection_id} (直接clip_ids)")
|
||||
try:
|
||||
update_data = {
|
||||
"clip_ids": new_clip_ids
|
||||
}
|
||||
response = requests.put(
|
||||
f"http://localhost:8000/api/v1/collections/{collection_id}",
|
||||
json=update_data,
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
print(f"📥 响应状态: {response.status_code}")
|
||||
if response.status_code == 200:
|
||||
print("✅ 方式3成功")
|
||||
else:
|
||||
print(f"❌ 方式3失败: {response.text}")
|
||||
except Exception as e:
|
||||
print(f"❌ 方式3异常: {e}")
|
||||
|
||||
# 方式4: 检查是否有其他端点
|
||||
print("\n🔄 方式4: 检查是否有其他端点")
|
||||
try:
|
||||
# 检查是否有 /projects/{project_id}/collections/{collection_id}/reorder 端点
|
||||
response = requests.patch(
|
||||
f"http://localhost:8000/api/v1/projects/{project_id}/collections/{collection_id}/reorder",
|
||||
json=new_clip_ids,
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
print(f"📥 响应状态: {response.status_code}")
|
||||
if response.status_code == 200:
|
||||
print("✅ 方式4成功")
|
||||
else:
|
||||
print(f"❌ 方式4失败: {response.text}")
|
||||
except Exception as e:
|
||||
print(f"❌ 方式4异常: {e}")
|
||||
|
||||
# 3. 检查后端日志中的请求
|
||||
print("\n3️⃣ 检查后端日志...")
|
||||
print("请查看后端日志,看看前端实际发送了什么请求")
|
||||
print("如果日志中没有看到任何请求,说明前端可能没有调用API")
|
||||
|
||||
if __name__ == "__main__":
|
||||
debug_frontend_request()
|
||||
@@ -1,97 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
前端状态调试脚本
|
||||
检查前端页面的实际状态
|
||||
"""
|
||||
import requests
|
||||
import time
|
||||
|
||||
def check_frontend_page():
|
||||
"""检查前端页面状态"""
|
||||
print("🔍 检查前端页面状态...")
|
||||
|
||||
try:
|
||||
# 获取前端页面
|
||||
response = requests.get("http://localhost:3000")
|
||||
print(f"前端页面状态码: {response.status_code}")
|
||||
|
||||
if response.status_code == 200:
|
||||
content = response.text
|
||||
print("✅ 前端页面正常加载")
|
||||
|
||||
# 检查是否包含React应用的关键元素
|
||||
if "react" in content.lower() or "app" in content.lower():
|
||||
print("✅ 检测到React应用")
|
||||
else:
|
||||
print("⚠️ 未检测到React应用特征")
|
||||
|
||||
# 检查页面大小
|
||||
print(f"页面大小: {len(content)} 字符")
|
||||
|
||||
else:
|
||||
print(f"❌ 前端页面加载失败: {response.status_code}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 检查前端页面失败: {e}")
|
||||
|
||||
def check_backend_health():
|
||||
"""检查后端健康状态"""
|
||||
print("\n🔍 检查后端健康状态...")
|
||||
|
||||
try:
|
||||
response = requests.get("http://localhost:8000/api/v1/health")
|
||||
print(f"后端健康检查: {response.status_code}")
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
print(f"后端状态: {data}")
|
||||
else:
|
||||
print(f"❌ 后端健康检查失败: {response.status_code}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 检查后端健康状态失败: {e}")
|
||||
|
||||
def check_projects_api():
|
||||
"""检查项目API"""
|
||||
print("\n🔍 检查项目API...")
|
||||
|
||||
try:
|
||||
response = requests.get("http://localhost:8000/api/v1/projects/")
|
||||
print(f"项目API状态: {response.status_code}")
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
items = data.get('items', [])
|
||||
print(f"项目数量: {len(items)}")
|
||||
|
||||
if len(items) > 0:
|
||||
print("📋 项目列表:")
|
||||
for i, project in enumerate(items):
|
||||
print(f" {i+1}. {project.get('name', 'Unknown')} - {project.get('status', 'Unknown')}")
|
||||
else:
|
||||
print("📭 没有项目")
|
||||
else:
|
||||
print(f"❌ 项目API失败: {response.status_code}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 检查项目API失败: {e}")
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("🚀 前端状态调试开始")
|
||||
print("=" * 50)
|
||||
|
||||
check_frontend_page()
|
||||
check_backend_health()
|
||||
check_projects_api()
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("✅ 调试完成")
|
||||
print("\n💡 建议:")
|
||||
print("1. 打开浏览器开发者工具查看控制台错误")
|
||||
print("2. 检查网络面板中的API请求")
|
||||
print("3. 查看React组件状态")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
调试步骤3的具体错误
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
project_root = Path(__file__).parent.parent
|
||||
backend_path = project_root / "backend"
|
||||
sys.path.insert(0, str(backend_path))
|
||||
|
||||
def debug_step3():
|
||||
"""调试步骤3"""
|
||||
print("开始调试步骤3...")
|
||||
|
||||
try:
|
||||
# 1. 测试导入
|
||||
print("1. 测试导入...")
|
||||
from pipeline.step3_scoring import ClipScorer
|
||||
print("✓ 导入成功")
|
||||
|
||||
# 2. 测试初始化
|
||||
print("\n2. 测试初始化...")
|
||||
scorer = ClipScorer()
|
||||
print("✓ 初始化成功")
|
||||
|
||||
# 3. 检查提示词
|
||||
print("\n3. 检查提示词...")
|
||||
if hasattr(scorer, 'recommendation_prompt'):
|
||||
print(f"✓ 提示词存在,长度: {len(scorer.recommendation_prompt)}")
|
||||
print(f" 前100字符: {scorer.recommendation_prompt[:100]}...")
|
||||
else:
|
||||
print("✗ 提示词不存在")
|
||||
return False
|
||||
|
||||
# 4. 测试方法存在
|
||||
print("\n4. 测试方法存在...")
|
||||
if hasattr(scorer, 'score_clips'):
|
||||
print("✓ score_clips方法存在")
|
||||
else:
|
||||
print("✗ score_clips方法不存在")
|
||||
return False
|
||||
|
||||
# 5. 测试配置
|
||||
print("\n5. 测试配置...")
|
||||
from pipeline.config import PROMPT_FILES
|
||||
if 'recommendation' in PROMPT_FILES:
|
||||
print(f"✓ recommendation键存在: {PROMPT_FILES['recommendation']}")
|
||||
if PROMPT_FILES['recommendation'].exists():
|
||||
print("✓ 文件存在")
|
||||
else:
|
||||
print("✗ 文件不存在")
|
||||
return False
|
||||
else:
|
||||
print("✗ recommendation键不存在")
|
||||
return False
|
||||
|
||||
# 6. 测试LLM客户端
|
||||
print("\n6. 测试LLM客户端...")
|
||||
if hasattr(scorer, 'llm_client'):
|
||||
print("✓ LLM客户端存在")
|
||||
else:
|
||||
print("✗ LLM客户端不存在")
|
||||
return False
|
||||
|
||||
print("\n✓ 所有测试通过!")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n✗ 调试失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("=" * 50)
|
||||
print("步骤3调试工具")
|
||||
print("=" * 50)
|
||||
|
||||
success = debug_step3()
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
if success:
|
||||
print("✓ 步骤3调试完成,未发现问题")
|
||||
return 0
|
||||
else:
|
||||
print("✗ 步骤3调试发现问题")
|
||||
return 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -1,458 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
字幕下载诊断工具
|
||||
帮助用户排查B站和YouTube字幕下载问题
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
import asyncio
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
# 设置环境变量
|
||||
import os
|
||||
os.environ['PYTHONPATH'] = str(project_root)
|
||||
|
||||
# 添加backend目录到路径
|
||||
backend_dir = project_root / "backend"
|
||||
sys.path.insert(0, str(backend_dir))
|
||||
|
||||
from backend.utils.bilibili_downloader import BilibiliDownloader, get_bilibili_video_info
|
||||
from backend.utils.speech_recognizer import get_available_speech_recognition_methods, SpeechRecognitionError
|
||||
import yt_dlp
|
||||
|
||||
# 配置日志
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class SubtitleDownloadDiagnostic:
|
||||
"""字幕下载诊断工具"""
|
||||
|
||||
def __init__(self):
|
||||
self.results = {}
|
||||
|
||||
async def diagnose_bilibili_subtitle(self, url: str, browser: Optional[str] = None) -> Dict[str, Any]:
|
||||
"""诊断B站字幕下载问题"""
|
||||
logger.info(f"开始诊断B站字幕下载: {url}")
|
||||
|
||||
result = {
|
||||
"url": url,
|
||||
"platform": "bilibili",
|
||||
"video_info": None,
|
||||
"subtitle_availability": {},
|
||||
"download_attempts": [],
|
||||
"recommendations": []
|
||||
}
|
||||
|
||||
try:
|
||||
# 1. 获取视频信息
|
||||
logger.info("1. 获取视频信息...")
|
||||
video_info = await get_bilibili_video_info(url, browser)
|
||||
result["video_info"] = {
|
||||
"title": video_info.title,
|
||||
"duration": video_info.duration,
|
||||
"uploader": video_info.uploader,
|
||||
"view_count": video_info.view_count
|
||||
}
|
||||
logger.info(f"✅ 视频信息获取成功: {video_info.title}")
|
||||
|
||||
# 2. 检查字幕可用性
|
||||
logger.info("2. 检查字幕可用性...")
|
||||
subtitle_check = await self._check_bilibili_subtitle_availability(url, browser)
|
||||
result["subtitle_availability"] = subtitle_check
|
||||
|
||||
# 3. 尝试不同的下载策略
|
||||
logger.info("3. 尝试不同的下载策略...")
|
||||
download_attempts = await self._test_bilibili_download_strategies(url, browser)
|
||||
result["download_attempts"] = download_attempts
|
||||
|
||||
# 4. 生成建议
|
||||
result["recommendations"] = self._generate_bilibili_recommendations(subtitle_check, download_attempts)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ B站诊断失败: {e}")
|
||||
result["error"] = str(e)
|
||||
result["recommendations"].append(f"诊断过程出错: {e}")
|
||||
|
||||
return result
|
||||
|
||||
async def diagnose_youtube_subtitle(self, url: str, browser: Optional[str] = None) -> Dict[str, Any]:
|
||||
"""诊断YouTube字幕下载问题"""
|
||||
logger.info(f"开始诊断YouTube字幕下载: {url}")
|
||||
|
||||
result = {
|
||||
"url": url,
|
||||
"platform": "youtube",
|
||||
"video_info": None,
|
||||
"subtitle_availability": {},
|
||||
"download_attempts": [],
|
||||
"recommendations": []
|
||||
}
|
||||
|
||||
try:
|
||||
# 1. 获取视频信息
|
||||
logger.info("1. 获取视频信息...")
|
||||
video_info = await self._get_youtube_video_info(url, browser)
|
||||
result["video_info"] = video_info
|
||||
logger.info(f"✅ 视频信息获取成功: {video_info.get('title', 'Unknown')}")
|
||||
|
||||
# 2. 检查字幕可用性
|
||||
logger.info("2. 检查字幕可用性...")
|
||||
subtitle_check = await self._check_youtube_subtitle_availability(url, browser)
|
||||
result["subtitle_availability"] = subtitle_check
|
||||
|
||||
# 3. 尝试不同的下载策略
|
||||
logger.info("3. 尝试不同的下载策略...")
|
||||
download_attempts = await self._test_youtube_download_strategies(url, browser)
|
||||
result["download_attempts"] = download_attempts
|
||||
|
||||
# 4. 生成建议
|
||||
result["recommendations"] = self._generate_youtube_recommendations(subtitle_check, download_attempts)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ YouTube诊断失败: {e}")
|
||||
result["error"] = str(e)
|
||||
result["recommendations"].append(f"诊断过程出错: {e}")
|
||||
|
||||
return result
|
||||
|
||||
async def _check_bilibili_subtitle_availability(self, url: str, browser: Optional[str] = None) -> Dict[str, Any]:
|
||||
"""检查B站字幕可用性"""
|
||||
try:
|
||||
ydl_opts = {
|
||||
'quiet': True,
|
||||
'no_warnings': True,
|
||||
}
|
||||
|
||||
if browser:
|
||||
ydl_opts['cookiesfrombrowser'] = (browser.lower(),)
|
||||
|
||||
def extract_info_sync(url, ydl_opts):
|
||||
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||
return ydl.extract_info(url, download=False)
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
info_dict = await loop.run_in_executor(None, extract_info_sync, url, ydl_opts)
|
||||
|
||||
subtitles = info_dict.get('subtitles', {})
|
||||
auto_subtitles = info_dict.get('automatic_captions', {})
|
||||
|
||||
return {
|
||||
"manual_subtitles": list(subtitles.keys()) if subtitles else [],
|
||||
"auto_subtitles": list(auto_subtitles.keys()) if auto_subtitles else [],
|
||||
"requires_login": len(subtitles) == 0 and len(auto_subtitles) == 0,
|
||||
"total_subtitle_tracks": len(subtitles) + len(auto_subtitles)
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"检查B站字幕可用性失败: {e}")
|
||||
return {"error": str(e)}
|
||||
|
||||
async def _check_youtube_subtitle_availability(self, url: str, browser: Optional[str] = None) -> Dict[str, Any]:
|
||||
"""检查YouTube字幕可用性"""
|
||||
try:
|
||||
ydl_opts = {
|
||||
'quiet': True,
|
||||
'no_warnings': True,
|
||||
}
|
||||
|
||||
if browser:
|
||||
ydl_opts['cookiesfrombrowser'] = (browser.lower(),)
|
||||
|
||||
def extract_info_sync(url, ydl_opts):
|
||||
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||
return ydl.extract_info(url, download=False)
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
info_dict = await loop.run_in_executor(None, extract_info_sync, url, ydl_opts)
|
||||
|
||||
subtitles = info_dict.get('subtitles', {})
|
||||
auto_subtitles = info_dict.get('automatic_captions', {})
|
||||
|
||||
return {
|
||||
"manual_subtitles": list(subtitles.keys()) if subtitles else [],
|
||||
"auto_subtitles": list(auto_subtitles.keys()) if auto_subtitles else [],
|
||||
"total_subtitle_tracks": len(subtitles) + len(auto_subtitles)
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"检查YouTube字幕可用性失败: {e}")
|
||||
return {"error": str(e)}
|
||||
|
||||
async def _test_bilibili_download_strategies(self, url: str, browser: Optional[str] = None) -> list:
|
||||
"""测试B站不同的下载策略"""
|
||||
strategies = [
|
||||
("默认策略", {"subtitleslangs": ["ai-zh"], "writeautomaticsub": False}),
|
||||
("多语言策略", {"subtitleslangs": ["ai-zh", "zh-Hans", "zh", "en"], "writeautomaticsub": True}),
|
||||
("无cookies策略", {"subtitleslangs": ["zh-Hans", "zh"], "writeautomaticsub": True, "cookies": None}),
|
||||
]
|
||||
|
||||
results = []
|
||||
temp_dir = Path("temp_diagnostic")
|
||||
temp_dir.mkdir(exist_ok=True)
|
||||
|
||||
for name, opts in strategies:
|
||||
try:
|
||||
logger.info(f"测试策略: {name}")
|
||||
|
||||
ydl_opts = {
|
||||
'format': 'best[ext=mp4]/best',
|
||||
'writesubtitles': True,
|
||||
'outtmpl': str(temp_dir / f'test_{name}.%(ext)s'),
|
||||
'noplaylist': True,
|
||||
'quiet': True,
|
||||
**opts
|
||||
}
|
||||
|
||||
if browser and opts.get("cookies") is not None:
|
||||
ydl_opts['cookiesfrombrowser'] = (browser.lower(),)
|
||||
|
||||
def download_sync(url, ydl_opts):
|
||||
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||
return ydl.download([url])
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
await loop.run_in_executor(None, download_sync, url, ydl_opts)
|
||||
|
||||
# 检查是否下载了字幕文件
|
||||
subtitle_files = list(temp_dir.glob("*.srt"))
|
||||
success = len(subtitle_files) > 0
|
||||
|
||||
results.append({
|
||||
"strategy": name,
|
||||
"success": success,
|
||||
"subtitle_files": [f.name for f in subtitle_files],
|
||||
"options": opts
|
||||
})
|
||||
|
||||
logger.info(f"✅ 策略 {name}: {'成功' if success else '失败'}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 策略 {name} 失败: {e}")
|
||||
results.append({
|
||||
"strategy": name,
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"options": opts
|
||||
})
|
||||
|
||||
# 清理临时文件
|
||||
for file in temp_dir.glob("*"):
|
||||
file.unlink()
|
||||
temp_dir.rmdir()
|
||||
|
||||
return results
|
||||
|
||||
async def _test_youtube_download_strategies(self, url: str, browser: Optional[str] = None) -> list:
|
||||
"""测试YouTube不同的下载策略"""
|
||||
strategies = [
|
||||
("默认策略", {"subtitleslangs": ["en", "zh-Hans"], "writeautomaticsub": True}),
|
||||
("多语言策略", {"subtitleslangs": ["en", "zh-Hans", "zh", "ja", "ko"], "writeautomaticsub": True}),
|
||||
("多格式策略", {"subtitleslangs": ["en", "zh-Hans"], "subtitlesformat": "vtt", "writeautomaticsub": True}),
|
||||
]
|
||||
|
||||
results = []
|
||||
temp_dir = Path("temp_diagnostic")
|
||||
temp_dir.mkdir(exist_ok=True)
|
||||
|
||||
for name, opts in strategies:
|
||||
try:
|
||||
logger.info(f"测试策略: {name}")
|
||||
|
||||
ydl_opts = {
|
||||
'format': 'best[ext=mp4]/best',
|
||||
'writesubtitles': True,
|
||||
'outtmpl': str(temp_dir / f'test_{name}.%(ext)s'),
|
||||
'noplaylist': True,
|
||||
'quiet': True,
|
||||
**opts
|
||||
}
|
||||
|
||||
if browser:
|
||||
ydl_opts['cookiesfrombrowser'] = (browser.lower(),)
|
||||
|
||||
def download_sync(url, ydl_opts):
|
||||
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||
return ydl.download([url])
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
await loop.run_in_executor(None, download_sync, url, ydl_opts)
|
||||
|
||||
# 检查是否下载了字幕文件
|
||||
subtitle_files = list(temp_dir.glob("*.srt")) + list(temp_dir.glob("*.vtt"))
|
||||
success = len(subtitle_files) > 0
|
||||
|
||||
results.append({
|
||||
"strategy": name,
|
||||
"success": success,
|
||||
"subtitle_files": [f.name for f in subtitle_files],
|
||||
"options": opts
|
||||
})
|
||||
|
||||
logger.info(f"✅ 策略 {name}: {'成功' if success else '失败'}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 策略 {name} 失败: {e}")
|
||||
results.append({
|
||||
"strategy": name,
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"options": opts
|
||||
})
|
||||
|
||||
# 清理临时文件
|
||||
for file in temp_dir.glob("*"):
|
||||
file.unlink()
|
||||
temp_dir.rmdir()
|
||||
|
||||
return results
|
||||
|
||||
async def _get_youtube_video_info(self, url: str, browser: Optional[str] = None) -> Dict[str, Any]:
|
||||
"""获取YouTube视频信息"""
|
||||
try:
|
||||
ydl_opts = {
|
||||
'quiet': True,
|
||||
'no_warnings': True,
|
||||
}
|
||||
|
||||
if browser:
|
||||
ydl_opts['cookiesfrombrowser'] = (browser.lower(),)
|
||||
|
||||
def extract_info_sync(url, ydl_opts):
|
||||
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||
return ydl.extract_info(url, download=False)
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
info_dict = await loop.run_in_executor(None, extract_info_sync, url, ydl_opts)
|
||||
|
||||
return {
|
||||
"title": info_dict.get('title', 'Unknown'),
|
||||
"duration": info_dict.get('duration', 0),
|
||||
"uploader": info_dict.get('uploader', 'Unknown'),
|
||||
"view_count": info_dict.get('view_count', 0),
|
||||
"upload_date": info_dict.get('upload_date', ''),
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取YouTube视频信息失败: {e}")
|
||||
return {"error": str(e)}
|
||||
|
||||
def _generate_bilibili_recommendations(self, subtitle_check: Dict, download_attempts: list) -> list:
|
||||
"""生成B站字幕下载建议"""
|
||||
recommendations = []
|
||||
|
||||
if subtitle_check.get("requires_login", False):
|
||||
recommendations.append("🔐 需要登录才能下载字幕,请选择浏览器并确保已登录B站")
|
||||
|
||||
if subtitle_check.get("total_subtitle_tracks", 0) == 0:
|
||||
recommendations.append("⚠️ 该视频可能没有字幕,建议使用语音识别生成字幕")
|
||||
|
||||
successful_strategies = [s for s in download_attempts if s.get("success", False)]
|
||||
if successful_strategies:
|
||||
best_strategy = successful_strategies[0]
|
||||
recommendations.append(f"✅ 推荐使用策略: {best_strategy['strategy']}")
|
||||
else:
|
||||
recommendations.append("❌ 所有下载策略都失败,建议检查网络连接和视频链接")
|
||||
|
||||
return recommendations
|
||||
|
||||
def _generate_youtube_recommendations(self, subtitle_check: Dict, download_attempts: list) -> list:
|
||||
"""生成YouTube字幕下载建议"""
|
||||
recommendations = []
|
||||
|
||||
if subtitle_check.get("total_subtitle_tracks", 0) == 0:
|
||||
recommendations.append("⚠️ 该视频可能没有字幕,建议使用语音识别生成字幕")
|
||||
|
||||
successful_strategies = [s for s in download_attempts if s.get("success", False)]
|
||||
if successful_strategies:
|
||||
best_strategy = successful_strategies[0]
|
||||
recommendations.append(f"✅ 推荐使用策略: {best_strategy['strategy']}")
|
||||
else:
|
||||
recommendations.append("❌ 所有下载策略都失败,建议检查网络连接和视频链接")
|
||||
|
||||
return recommendations
|
||||
|
||||
def check_speech_recognition_setup(self) -> Dict[str, Any]:
|
||||
"""检查语音识别设置"""
|
||||
logger.info("检查语音识别设置...")
|
||||
|
||||
available_methods = get_available_speech_recognition_methods()
|
||||
|
||||
result = {
|
||||
"available_methods": available_methods,
|
||||
"recommendations": []
|
||||
}
|
||||
|
||||
if not any(available_methods.values()):
|
||||
result["recommendations"].append("❌ 没有可用的语音识别服务")
|
||||
result["recommendations"].append("💡 建议安装Whisper: pip install openai-whisper")
|
||||
result["recommendations"].append("💡 同时安装ffmpeg: brew install ffmpeg (macOS) 或 sudo apt install ffmpeg (Ubuntu)")
|
||||
else:
|
||||
available = [k for k, v in available_methods.items() if v]
|
||||
result["recommendations"].append(f"✅ 可用的语音识别服务: {', '.join(available)}")
|
||||
|
||||
return result
|
||||
|
||||
async def main():
|
||||
"""主函数"""
|
||||
if len(sys.argv) < 2:
|
||||
print("用法:")
|
||||
print(" python debug_subtitle_download.py <url> [browser]")
|
||||
print(" python debug_subtitle_download.py --check-speech")
|
||||
print("")
|
||||
print("示例:")
|
||||
print(" python debug_subtitle_download.py https://www.bilibili.com/video/BV1xx411c7mu")
|
||||
print(" python debug_subtitle_download.py https://www.youtube.com/watch?v=dQw4w9WgXcQ chrome")
|
||||
print(" python debug_subtitle_download.py --check-speech")
|
||||
return
|
||||
|
||||
diagnostic = SubtitleDownloadDiagnostic()
|
||||
|
||||
if sys.argv[1] == "--check-speech":
|
||||
# 检查语音识别设置
|
||||
speech_result = diagnostic.check_speech_recognition_setup()
|
||||
print("\n🎤 语音识别设置检查结果:")
|
||||
print(json.dumps(speech_result, indent=2, ensure_ascii=False))
|
||||
return
|
||||
|
||||
url = sys.argv[1]
|
||||
browser = sys.argv[2] if len(sys.argv) > 2 else None
|
||||
|
||||
print(f"🔍 开始诊断字幕下载问题...")
|
||||
print(f"URL: {url}")
|
||||
print(f"浏览器: {browser or '未指定'}")
|
||||
print("=" * 50)
|
||||
|
||||
# 检查语音识别设置
|
||||
speech_result = diagnostic.check_speech_recognition_setup()
|
||||
print("\n🎤 语音识别设置:")
|
||||
print(json.dumps(speech_result, indent=2, ensure_ascii=False))
|
||||
|
||||
# 根据URL类型选择诊断方法
|
||||
if "bilibili.com" in url or "b23.tv" in url:
|
||||
result = await diagnostic.diagnose_bilibili_subtitle(url, browser)
|
||||
elif "youtube.com" in url or "youtu.be" in url:
|
||||
result = await diagnostic.diagnose_youtube_subtitle(url, browser)
|
||||
else:
|
||||
print("❌ 不支持的URL格式,请提供B站或YouTube链接")
|
||||
return
|
||||
|
||||
print("\n📊 诊断结果:")
|
||||
print(json.dumps(result, indent=2, ensure_ascii=False))
|
||||
|
||||
# 保存结果到文件
|
||||
output_file = f"subtitle_diagnostic_{result['platform']}_{Path(url).name}.json"
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(result, f, indent=2, ensure_ascii=False)
|
||||
|
||||
print(f"\n💾 诊断结果已保存到: {output_file}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
最终诊断合集排序问题
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
|
||||
def final_diagnosis():
|
||||
"""最终诊断合集排序问题"""
|
||||
|
||||
collection_id = "0e181e1a-52c2-42c2-9481-cc306e3b27f9"
|
||||
|
||||
print("🔍 最终诊断合集排序问题")
|
||||
print("=" * 60)
|
||||
|
||||
# 1. 检查后端API是否正常工作
|
||||
print("\n1️⃣ 检查后端API是否正常工作...")
|
||||
try:
|
||||
# 获取当前状态
|
||||
response = requests.get(f"http://localhost:8000/api/v1/collections/{collection_id}")
|
||||
if response.status_code == 200:
|
||||
collection = response.json()
|
||||
current_clip_ids = collection.get('clip_ids', [])
|
||||
print(f"✅ 当前clip_ids: {current_clip_ids}")
|
||||
|
||||
if len(current_clip_ids) >= 2:
|
||||
# 测试排序API
|
||||
new_clip_ids = current_clip_ids[1:] + current_clip_ids[:1]
|
||||
response = requests.patch(
|
||||
f"http://localhost:8000/api/v1/collections/{collection_id}/reorder",
|
||||
json=new_clip_ids,
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
print(f"✅ 排序API正常工作: {result.get('clip_ids', [])}")
|
||||
|
||||
# 恢复原始顺序
|
||||
response = requests.patch(
|
||||
f"http://localhost:8000/api/v1/collections/{collection_id}/reorder",
|
||||
json=current_clip_ids,
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
if response.status_code == 200:
|
||||
print("✅ 恢复原始顺序成功")
|
||||
else:
|
||||
print("❌ 恢复原始顺序失败")
|
||||
else:
|
||||
print(f"❌ 排序API失败: {response.status_code} - {response.text}")
|
||||
else:
|
||||
print("⚠️ clip_ids数量不足,无法测试排序")
|
||||
else:
|
||||
print(f"❌ 获取合集失败: {response.status_code}")
|
||||
except Exception as e:
|
||||
print(f"❌ 后端API检查异常: {e}")
|
||||
|
||||
# 2. 检查前端可能的问题
|
||||
print("\n2️⃣ 检查前端可能的问题...")
|
||||
print("💡 后端API完全正常工作,问题可能在前端:")
|
||||
print(" 1. 前端JavaScript错误")
|
||||
print(" 2. 前端缓存问题")
|
||||
print(" 3. 前端使用了旧版本的代码")
|
||||
print(" 4. 前端网络请求被拦截")
|
||||
print(" 5. 前端组件没有正确绑定事件")
|
||||
|
||||
# 3. 提供解决方案
|
||||
print("\n3️⃣ 解决方案...")
|
||||
print("🔧 请按以下步骤排查:")
|
||||
print(" 1. 打开浏览器开发者工具")
|
||||
print(" 2. 查看Console面板是否有JavaScript错误")
|
||||
print(" 3. 查看Network面板是否有网络请求")
|
||||
print(" 4. 尝试拖拽排序,观察是否有请求发送")
|
||||
print(" 5. 如果没有任何请求,说明前端没有调用API")
|
||||
print(" 6. 如果有请求但失败,查看具体的错误信息")
|
||||
|
||||
# 4. 前端调试建议
|
||||
print("\n4️⃣ 前端调试建议...")
|
||||
print("🐛 调试步骤:")
|
||||
print(" 1. 在浏览器控制台中输入以下代码检查前端状态:")
|
||||
print(" console.log('前端调试信息')")
|
||||
print(" 2. 检查前端store是否正确导入:")
|
||||
print(" console.log(window.store)")
|
||||
print(" 3. 检查API方法是否存在:")
|
||||
print(" console.log(window.projectApi)")
|
||||
print(" 4. 手动触发排序API调用:")
|
||||
print(" window.projectApi.reorderCollectionClips('collection_id', ['clip1', 'clip2'])")
|
||||
|
||||
# 5. 后端确认
|
||||
print("\n5️⃣ 后端确认...")
|
||||
print("✅ 后端API完全正常工作:")
|
||||
print(" - PATCH /collections/{collection_id}/reorder ✅")
|
||||
print(" - 数据库更新正常 ✅")
|
||||
print(" - 响应格式正确 ✅")
|
||||
print(" - CORS设置正常 ✅")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("🎯 诊断结论:")
|
||||
print("✅ 后端API完全正常")
|
||||
print("❌ 问题在前端,需要进一步调试")
|
||||
print("\n💡 建议:")
|
||||
print("1. 检查浏览器控制台的JavaScript错误")
|
||||
print("2. 清除浏览器缓存并重新加载页面")
|
||||
print("3. 重启前端开发服务器")
|
||||
print("4. 检查前端代码是否有语法错误")
|
||||
|
||||
if __name__ == "__main__":
|
||||
final_diagnosis()
|
||||
@@ -1,177 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
最终测试合集排序功能
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
from typing import List
|
||||
|
||||
def final_test_reorder():
|
||||
"""最终测试合集排序功能"""
|
||||
|
||||
# 测试数据
|
||||
project_id = "5c48803d-0aa7-48d7-a270-2b33e4954f25"
|
||||
collection_id = "0e181e1a-52c2-42c2-9481-cc306e3b27f9"
|
||||
|
||||
print("🎯 最终测试合集排序功能")
|
||||
print("=" * 60)
|
||||
|
||||
# 1. 获取初始状态
|
||||
print("\n1️⃣ 获取初始状态...")
|
||||
try:
|
||||
response = requests.get(f"http://localhost:8000/api/v1/collections/{collection_id}")
|
||||
if response.status_code == 200:
|
||||
collection = response.json()
|
||||
print(f"✅ 合集: {collection['name']}")
|
||||
initial_clip_ids = collection.get('clip_ids', [])
|
||||
print(f"📋 初始clip_ids: {initial_clip_ids}")
|
||||
else:
|
||||
print(f"❌ 获取合集失败: {response.status_code}")
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"❌ 获取合集异常: {e}")
|
||||
return
|
||||
|
||||
if len(initial_clip_ids) < 2:
|
||||
print("⚠️ clip_ids数量不足,无法测试排序")
|
||||
return
|
||||
|
||||
# 2. 测试所有可能的API调用方式
|
||||
print("\n2️⃣ 测试所有可能的API调用方式...")
|
||||
|
||||
# 方式1: 新的排序端点
|
||||
print("\n🔄 方式1: PATCH /collections/{collection_id}/reorder")
|
||||
new_clip_ids_1 = initial_clip_ids[1:] + initial_clip_ids[:1]
|
||||
try:
|
||||
response = requests.patch(
|
||||
f"http://localhost:8000/api/v1/collections/{collection_id}/reorder",
|
||||
json=new_clip_ids_1,
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
print(f"📥 响应状态: {response.status_code}")
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
print(f"✅ 方式1成功: {result.get('clip_ids', [])}")
|
||||
else:
|
||||
print(f"❌ 方式1失败: {response.text}")
|
||||
except Exception as e:
|
||||
print(f"❌ 方式1异常: {e}")
|
||||
|
||||
# 方式2: PUT方式(metadata格式)
|
||||
print("\n🔄 方式2: PUT /collections/{collection_id} (metadata格式)")
|
||||
new_clip_ids_2 = new_clip_ids_1[1:] + new_clip_ids_1[:1]
|
||||
try:
|
||||
update_data = {
|
||||
"metadata": {
|
||||
"clip_ids": new_clip_ids_2
|
||||
}
|
||||
}
|
||||
response = requests.put(
|
||||
f"http://localhost:8000/api/v1/collections/{collection_id}",
|
||||
json=update_data,
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
print(f"📥 响应状态: {response.status_code}")
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
print(f"✅ 方式2成功: {result.get('clip_ids', [])}")
|
||||
else:
|
||||
print(f"❌ 方式2失败: {response.text}")
|
||||
except Exception as e:
|
||||
print(f"❌ 方式2异常: {e}")
|
||||
|
||||
# 方式3: PUT方式(直接clip_ids)
|
||||
print("\n🔄 方式3: PUT /collections/{collection_id} (直接clip_ids)")
|
||||
new_clip_ids_3 = new_clip_ids_2[1:] + new_clip_ids_2[:1]
|
||||
try:
|
||||
update_data = {
|
||||
"clip_ids": new_clip_ids_3
|
||||
}
|
||||
response = requests.put(
|
||||
f"http://localhost:8000/api/v1/collections/{collection_id}",
|
||||
json=update_data,
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
print(f"📥 响应状态: {response.status_code}")
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
print(f"✅ 方式3成功: {result.get('clip_ids', [])}")
|
||||
else:
|
||||
print(f"❌ 方式3失败: {response.text}")
|
||||
except Exception as e:
|
||||
print(f"❌ 方式3异常: {e}")
|
||||
|
||||
# 3. 恢复到原始顺序
|
||||
print("\n3️⃣ 恢复到原始顺序...")
|
||||
try:
|
||||
response = requests.patch(
|
||||
f"http://localhost:8000/api/v1/collections/{collection_id}/reorder",
|
||||
json=initial_clip_ids,
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
print(f"📥 响应状态: {response.status_code}")
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
print(f"✅ 恢复成功: {result.get('clip_ids', [])}")
|
||||
else:
|
||||
print(f"❌ 恢复失败: {response.text}")
|
||||
except Exception as e:
|
||||
print(f"❌ 恢复异常: {e}")
|
||||
|
||||
# 4. 最终验证
|
||||
print("\n4️⃣ 最终验证...")
|
||||
try:
|
||||
response = requests.get(f"http://localhost:8000/api/v1/collections/{collection_id}")
|
||||
if response.status_code == 200:
|
||||
final_collection = response.json()
|
||||
final_clip_ids = final_collection.get('clip_ids', [])
|
||||
print(f"✅ 最终clip_ids: {final_clip_ids}")
|
||||
|
||||
if final_clip_ids == initial_clip_ids:
|
||||
print("✅ 最终验证成功!数据已恢复到原始顺序")
|
||||
else:
|
||||
print("❌ 最终验证失败,数据未恢复到原始顺序")
|
||||
else:
|
||||
print(f"❌ 最终验证失败: {response.status_code}")
|
||||
except Exception as e:
|
||||
print(f"❌ 最终验证异常: {e}")
|
||||
|
||||
# 5. 测试前端API兼容性
|
||||
print("\n5️⃣ 测试前端API兼容性...")
|
||||
try:
|
||||
response = requests.get(f"http://localhost:8000/api/v1/collections/?project_id={project_id}")
|
||||
if response.status_code == 200:
|
||||
collections_data = response.json()
|
||||
collections = collections_data.get('items', []) if isinstance(collections_data, dict) else collections_data
|
||||
|
||||
target_collection = None
|
||||
for collection in collections:
|
||||
if collection.get('id') == collection_id:
|
||||
target_collection = collection
|
||||
break
|
||||
|
||||
if target_collection:
|
||||
print(f"✅ 前端API兼容性正常: {target_collection.get('clip_ids', [])}")
|
||||
else:
|
||||
print("❌ 前端API兼容性异常:未找到目标合集")
|
||||
else:
|
||||
print(f"❌ 前端API兼容性测试失败: {response.status_code}")
|
||||
except Exception as e:
|
||||
print(f"❌ 前端API兼容性测试异常: {e}")
|
||||
|
||||
# 6. 总结
|
||||
print("\n" + "=" * 60)
|
||||
print("🎉 合集排序功能最终测试完成!")
|
||||
print("\n📋 测试总结:")
|
||||
print("✅ 后端API端点正常工作")
|
||||
print("✅ 数据库更新正常")
|
||||
print("✅ 前端API兼容性正常")
|
||||
print("✅ 多种调用方式都支持")
|
||||
print("\n💡 如果前端仍然失败,可能的原因:")
|
||||
print("1. 前端缓存问题 - 请清除浏览器缓存或重启前端服务")
|
||||
print("2. 前端使用了旧版本的代码 - 请重新构建前端")
|
||||
print("3. 网络问题 - 请检查网络连接")
|
||||
|
||||
if __name__ == "__main__":
|
||||
final_test_reorder()
|
||||
@@ -1,124 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
修复所有项目的数据同步问题
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
# 添加backend目录到路径
|
||||
backend_dir = project_root / "backend"
|
||||
sys.path.insert(0, str(backend_dir))
|
||||
|
||||
from core.database import SessionLocal
|
||||
from services.data_sync_service import DataSyncService
|
||||
from core.config import get_data_directory
|
||||
|
||||
def fix_all_projects():
|
||||
"""修复所有项目的数据同步问题"""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
data_sync_service = DataSyncService(db)
|
||||
data_dir = get_data_directory()
|
||||
projects_dir = data_dir / "projects"
|
||||
|
||||
print(f"开始修复所有项目数据...")
|
||||
print(f"数据目录: {data_dir}")
|
||||
|
||||
fixed_projects = []
|
||||
failed_projects = []
|
||||
|
||||
# 遍历所有项目目录
|
||||
for project_dir in projects_dir.iterdir():
|
||||
if project_dir.is_dir() and not project_dir.name.startswith('.'):
|
||||
project_id = project_dir.name
|
||||
try:
|
||||
print(f"\n处理项目: {project_id}")
|
||||
|
||||
# 同步项目数据
|
||||
result = data_sync_service.sync_project_from_filesystem(project_id, project_dir)
|
||||
|
||||
if result["success"]:
|
||||
clips_count = result.get('clips_synced', 0)
|
||||
collections_count = result.get('collections_synced', 0)
|
||||
print(f" ✅ 成功: 切片 {clips_count} 个, 合集 {collections_count} 个")
|
||||
fixed_projects.append({
|
||||
"project_id": project_id,
|
||||
"clips": clips_count,
|
||||
"collections": collections_count
|
||||
})
|
||||
else:
|
||||
print(f" ❌ 失败: {result.get('error', '未知错误')}")
|
||||
failed_projects.append({"project_id": project_id, "error": result.get('error')})
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ 异常: {str(e)}")
|
||||
failed_projects.append({"project_id": project_id, "error": str(e)})
|
||||
|
||||
# 输出总结
|
||||
print(f"\n📊 修复完成!")
|
||||
print(f" 成功修复: {len(fixed_projects)} 个项目")
|
||||
print(f" 失败: {len(failed_projects)} 个项目")
|
||||
|
||||
if fixed_projects:
|
||||
print(f"\n✅ 成功修复的项目:")
|
||||
for project in fixed_projects:
|
||||
print(f" - {project['project_id']}: 切片 {project['clips']} 个, 合集 {project['collections']} 个")
|
||||
|
||||
if failed_projects:
|
||||
print(f"\n❌ 失败的项目:")
|
||||
for project in failed_projects:
|
||||
print(f" - {project['project_id']}: {project['error']}")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 修复过程中发生错误: {str(e)}")
|
||||
return False
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
def check_database_status():
|
||||
"""检查数据库状态"""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
from models.project import Project
|
||||
from models.clip import Clip
|
||||
from models.collection import Collection
|
||||
|
||||
# 统计数据库中的数据
|
||||
total_projects = db.query(Project).count()
|
||||
total_clips = db.query(Clip).count()
|
||||
total_collections = db.query(Collection).count()
|
||||
|
||||
print(f"\n📊 数据库状态:")
|
||||
print(f" 项目总数: {total_projects}")
|
||||
print(f" 切片总数: {total_clips}")
|
||||
print(f" 合集总数: {total_collections}")
|
||||
|
||||
# 显示有数据的项目
|
||||
print(f"\n📋 有数据的项目:")
|
||||
projects = db.query(Project).all()
|
||||
for project in projects:
|
||||
clips_count = db.query(Clip).filter(Clip.project_id == project.id).count()
|
||||
collections_count = db.query(Collection).filter(Collection.project_id == project.id).count()
|
||||
if clips_count > 0 or collections_count > 0:
|
||||
print(f" - {project.id}: {project.name} (切片: {clips_count}, 合集: {collections_count})")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 检查数据库状态失败: {str(e)}")
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("🔧 开始修复所有项目的数据同步问题...")
|
||||
success = fix_all_projects()
|
||||
if success:
|
||||
check_database_status()
|
||||
else:
|
||||
print("❌ 修复失败")
|
||||
@@ -1,161 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
修复合集中的clip_ids映射问题
|
||||
将chunk_index映射到实际的切片UUID
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
# 设置环境变量
|
||||
import os
|
||||
os.environ['PYTHONPATH'] = str(project_root)
|
||||
|
||||
# 添加backend目录到路径
|
||||
backend_dir = project_root / "backend"
|
||||
sys.path.insert(0, str(backend_dir))
|
||||
|
||||
from backend.core.database import SessionLocal
|
||||
from backend.models.clip import Clip
|
||||
from backend.models.collection import Collection
|
||||
|
||||
# 配置日志
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def fix_collection_clip_ids(project_id: str):
|
||||
"""修复合集中的clip_ids映射"""
|
||||
print(f"🔧 修复合集中的clip_ids映射: {project_id}")
|
||||
|
||||
try:
|
||||
db = SessionLocal()
|
||||
|
||||
try:
|
||||
# 获取项目的所有切片,按chunk_index排序
|
||||
clips = db.query(Clip).filter(Clip.project_id == project_id).all()
|
||||
print(f"📊 找到 {len(clips)} 个切片")
|
||||
|
||||
# 创建metadata_id到clip_id的映射
|
||||
metadata_id_to_clip_mapping = {}
|
||||
for clip in clips:
|
||||
metadata = clip.clip_metadata or {}
|
||||
metadata_id = metadata.get('id')
|
||||
if metadata_id:
|
||||
metadata_id_to_clip_mapping[str(metadata_id)] = clip.id
|
||||
print(f" 映射: metadata_id {metadata_id} -> clip_id {clip.id}")
|
||||
|
||||
# 获取项目的所有合集
|
||||
collections = db.query(Collection).filter(Collection.project_id == project_id).all()
|
||||
print(f"📊 找到 {len(collections)} 个合集")
|
||||
|
||||
for collection in collections:
|
||||
try:
|
||||
# 获取元数据文件路径
|
||||
metadata_file = collection.collection_metadata.get('metadata_file') if collection.collection_metadata else None
|
||||
|
||||
if not metadata_file or not Path(metadata_file).exists():
|
||||
print(f"⚠️ 合集 {collection.id} 的元数据文件不存在: {metadata_file}")
|
||||
continue
|
||||
|
||||
# 读取元数据文件
|
||||
with open(metadata_file, 'r', encoding='utf-8') as f:
|
||||
metadata_data = json.load(f)
|
||||
|
||||
# 获取原始的clip_ids(metadata_id)
|
||||
original_clip_ids = metadata_data.get('clip_ids', [])
|
||||
print(f" 合集 {collection.id} 原始clip_ids: {original_clip_ids}")
|
||||
|
||||
# 映射到实际的clip_id
|
||||
mapped_clip_ids = []
|
||||
for metadata_id in original_clip_ids:
|
||||
if metadata_id in metadata_id_to_clip_mapping:
|
||||
mapped_clip_ids.append(metadata_id_to_clip_mapping[metadata_id])
|
||||
else:
|
||||
print(f" ⚠️ 未找到metadata_id {metadata_id} 对应的clip_id")
|
||||
|
||||
print(f" 映射后的clip_ids: {mapped_clip_ids}")
|
||||
|
||||
# 更新数据库中的clip_ids(通过collection_metadata)
|
||||
if collection.collection_metadata:
|
||||
collection.collection_metadata['clip_ids'] = mapped_clip_ids
|
||||
|
||||
print(f"✅ 更新合集 {collection.id}: {collection.name}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 更新合集 {collection.id} 失败: {e}")
|
||||
continue
|
||||
|
||||
# 提交更改
|
||||
db.commit()
|
||||
print(f"✅ 成功修复 {len(collections)} 个合集的clip_ids映射")
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 修复失败: {e}")
|
||||
|
||||
def test_collection_data(project_id: str):
|
||||
"""测试合集数据"""
|
||||
print(f"🧪 测试合集数据: {project_id}")
|
||||
|
||||
try:
|
||||
import requests
|
||||
|
||||
# 测试合集API
|
||||
collections_response = requests.get(f"http://localhost:8000/api/v1/collections/?project_id={project_id}")
|
||||
if collections_response.status_code == 200:
|
||||
collections_data = collections_response.json()
|
||||
collections = collections_data.get('items', [])
|
||||
print(f"✅ 合集API返回 {len(collections)} 个合集")
|
||||
|
||||
for collection in collections:
|
||||
print(f" 合集: {collection['name']}")
|
||||
print(f" - clip_ids: {collection.get('clip_ids', [])}")
|
||||
print(f" - total_clips: {collection.get('total_clips', 0)}")
|
||||
|
||||
# 检查clip_ids是否对应实际的切片
|
||||
clip_ids = collection.get('clip_ids', [])
|
||||
if clip_ids:
|
||||
clips_response = requests.get(f"http://localhost:8000/api/v1/clips/?project_id={project_id}")
|
||||
if clips_response.status_code == 200:
|
||||
clips_data = clips_response.json()
|
||||
all_clip_ids = [clip['id'] for clip in clips_data.get('items', [])]
|
||||
|
||||
valid_clips = [clip_id for clip_id in clip_ids if clip_id in all_clip_ids]
|
||||
print(f" - 有效切片: {len(valid_clips)}/{len(clip_ids)}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 测试失败: {e}")
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="修复合集中的clip_ids映射")
|
||||
parser.add_argument("--project-id", type=str, required=True, help="项目ID")
|
||||
parser.add_argument("--test-only", action="store_true", help="仅测试数据")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
project_id = args.project_id
|
||||
|
||||
if args.test_only:
|
||||
# 仅测试数据
|
||||
test_collection_data(project_id)
|
||||
else:
|
||||
# 修复并测试
|
||||
print("🔧 开始修复clip_ids映射...")
|
||||
fix_collection_clip_ids(project_id)
|
||||
|
||||
print("\n🧪 测试修复结果...")
|
||||
test_collection_data(project_id)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,159 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
修复数据存储问题
|
||||
手动触发数据存储,解决前端显示0个切片和0个合集的问题
|
||||
"""
|
||||
|
||||
import sys
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
# 设置环境变量
|
||||
import os
|
||||
os.environ['PYTHONPATH'] = str(project_root)
|
||||
|
||||
# 添加backend目录到路径
|
||||
backend_dir = project_root / "backend"
|
||||
sys.path.insert(0, str(backend_dir))
|
||||
|
||||
from backend.core.database import SessionLocal
|
||||
from backend.services.pipeline_adapter import PipelineAdapter
|
||||
from backend.models.project import Project
|
||||
|
||||
# 配置日志
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def fix_project_data_storage(project_id: str):
|
||||
"""修复项目数据存储"""
|
||||
print(f"🔧 修复项目数据存储: {project_id}")
|
||||
|
||||
try:
|
||||
db = SessionLocal()
|
||||
|
||||
try:
|
||||
# 验证项目是否存在
|
||||
project = db.query(Project).filter(Project.id == project_id).first()
|
||||
if not project:
|
||||
print(f"❌ 项目不存在: {project_id}")
|
||||
return False
|
||||
|
||||
print(f"✅ 项目存在: {project.name}")
|
||||
|
||||
# 创建Pipeline适配器
|
||||
adapter = PipelineAdapter(db, None, project_id)
|
||||
|
||||
# 获取项目目录
|
||||
project_dir = adapter.data_dir / "projects" / project_id
|
||||
|
||||
# 检查数据文件是否存在
|
||||
clips_file = project_dir / "step4_title" / "step4_title.json"
|
||||
collections_file = project_dir / "step5_clustering" / "step5_clustering.json"
|
||||
|
||||
if not clips_file.exists():
|
||||
print(f"❌ 切片数据文件不存在: {clips_file}")
|
||||
return False
|
||||
|
||||
if not collections_file.exists():
|
||||
print(f"❌ 合集数据文件不存在: {collections_file}")
|
||||
return False
|
||||
|
||||
print(f"✅ 数据文件存在")
|
||||
|
||||
# 保存切片数据到数据库
|
||||
print("📝 保存切片数据到数据库...")
|
||||
adapter._save_clips_to_database(project_id, clips_file)
|
||||
|
||||
# 保存合集数据到数据库
|
||||
print("📝 保存合集数据到数据库...")
|
||||
adapter._save_collections_to_database(project_id, collections_file)
|
||||
|
||||
print("✅ 数据存储修复完成")
|
||||
return True
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 修复失败: {e}")
|
||||
return False
|
||||
|
||||
def check_database_data(project_id: str):
|
||||
"""检查数据库中的数据"""
|
||||
print(f"🔍 检查数据库数据: {project_id}")
|
||||
|
||||
try:
|
||||
db = SessionLocal()
|
||||
|
||||
try:
|
||||
# 检查切片数据
|
||||
from backend.models.clip import Clip
|
||||
clips = db.query(Clip).filter(Clip.project_id == project_id).all()
|
||||
print(f"📊 数据库中的切片数量: {len(clips)}")
|
||||
|
||||
if clips:
|
||||
for clip in clips[:3]: # 显示前3个切片
|
||||
print(f" - {clip.title} (ID: {clip.id})")
|
||||
|
||||
# 检查合集数据
|
||||
from backend.models.collection import Collection
|
||||
collections = db.query(Collection).filter(Collection.project_id == project_id).all()
|
||||
print(f"📊 数据库中的合集数量: {len(collections)}")
|
||||
|
||||
if collections:
|
||||
for collection in collections:
|
||||
print(f" - {collection.name} (ID: {collection.id})")
|
||||
|
||||
return len(clips), len(collections)
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 检查失败: {e}")
|
||||
return 0, 0
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="修复数据存储问题")
|
||||
parser.add_argument("--project-id", type=str, required=True, help="项目ID")
|
||||
parser.add_argument("--check-only", action="store_true", help="仅检查数据,不修复")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
project_id = args.project_id
|
||||
|
||||
if args.check_only:
|
||||
# 仅检查数据
|
||||
check_database_data(project_id)
|
||||
else:
|
||||
# 检查并修复数据
|
||||
print("🔍 修复前检查...")
|
||||
clips_before, collections_before = check_database_data(project_id)
|
||||
|
||||
print("\n🔧 开始修复...")
|
||||
success = fix_project_data_storage(project_id)
|
||||
|
||||
if success:
|
||||
print("\n🔍 修复后检查...")
|
||||
clips_after, collections_after = check_database_data(project_id)
|
||||
|
||||
print(f"\n📊 修复结果:")
|
||||
print(f" 切片: {clips_before} -> {clips_after}")
|
||||
print(f" 合集: {collections_before} -> {collections_after}")
|
||||
|
||||
if clips_after > clips_before or collections_after > collections_before:
|
||||
print("✅ 数据存储修复成功!")
|
||||
else:
|
||||
print("⚠️ 数据存储修复可能失败,请检查日志")
|
||||
else:
|
||||
print("❌ 数据存储修复失败")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,121 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
数据迁移脚本 - 将文件系统数据迁移到数据库
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
# 设置环境变量
|
||||
import os
|
||||
os.environ['PYTHONPATH'] = str(project_root)
|
||||
|
||||
from backend.core.database import SessionLocal
|
||||
from backend.services.data_sync_service import DataSyncService
|
||||
from backend.core.config import get_data_directory
|
||||
|
||||
def migrate_project_data(project_id: str):
|
||||
"""迁移单个项目的数据到数据库"""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
data_sync_service = DataSyncService(db)
|
||||
data_dir = get_data_directory()
|
||||
project_dir = data_dir / "projects" / project_id
|
||||
|
||||
if not project_dir.exists():
|
||||
print(f"❌ 项目目录不存在: {project_dir}")
|
||||
return False
|
||||
|
||||
print(f"开始迁移项目: {project_id}")
|
||||
|
||||
# 同步数据到数据库
|
||||
result = data_sync_service.sync_project_data(project_id, project_dir)
|
||||
|
||||
print(f"✅ 迁移完成: {result}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 迁移失败: {str(e)}")
|
||||
return False
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
def migrate_all_projects():
|
||||
"""迁移所有项目的数据到数据库"""
|
||||
data_dir = get_data_directory()
|
||||
projects_dir = data_dir / "projects"
|
||||
|
||||
if not projects_dir.exists():
|
||||
print("❌ 项目目录不存在")
|
||||
return
|
||||
|
||||
success_count = 0
|
||||
total_count = 0
|
||||
|
||||
for project_dir in projects_dir.iterdir():
|
||||
if project_dir.is_dir() and not project_dir.name.startswith('.'):
|
||||
total_count += 1
|
||||
if migrate_project_data(project_dir.name):
|
||||
success_count += 1
|
||||
|
||||
print(f"\n✅ 迁移完成: {success_count}/{total_count} 个项目成功迁移")
|
||||
|
||||
def check_database_data():
|
||||
"""检查数据库中的数据"""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
from backend.models.project import Project
|
||||
from backend.models.collection import Collection
|
||||
from backend.models.clip import Clip
|
||||
|
||||
# 检查项目数量
|
||||
projects_count = db.query(Project).count()
|
||||
print(f"数据库中的项目数量: {projects_count}")
|
||||
|
||||
# 检查合集数量
|
||||
collections_count = db.query(Collection).count()
|
||||
print(f"数据库中的合集数量: {collections_count}")
|
||||
|
||||
# 检查切片数量
|
||||
clips_count = db.query(Clip).count()
|
||||
print(f"数据库中的切片数量: {clips_count}")
|
||||
|
||||
# 显示一些示例数据
|
||||
if collections_count > 0:
|
||||
print("\n示例合集数据:")
|
||||
collections = db.query(Collection).limit(3).all()
|
||||
for collection in collections:
|
||||
print(f" - {collection.name} (ID: {collection.id})")
|
||||
metadata = collection.collection_metadata or {}
|
||||
clip_ids = metadata.get('clip_ids', [])
|
||||
print(f" 包含切片: {clip_ids}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 检查数据库数据失败: {str(e)}")
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) > 1:
|
||||
if sys.argv[1] == "check":
|
||||
check_database_data()
|
||||
elif sys.argv[1] == "migrate":
|
||||
if len(sys.argv) > 2:
|
||||
migrate_project_data(sys.argv[2])
|
||||
else:
|
||||
migrate_all_projects()
|
||||
else:
|
||||
print("用法:")
|
||||
print(" python scripts/migrate_to_database.py check # 检查数据库数据")
|
||||
print(" python scripts/migrate_to_database.py migrate # 迁移所有项目")
|
||||
print(" python scripts/migrate_to_database.py migrate <project_id> # 迁移指定项目")
|
||||
else:
|
||||
print("用法:")
|
||||
print(" python scripts/migrate_to_database.py check # 检查数据库数据")
|
||||
print(" python scripts/migrate_to_database.py migrate # 迁移所有项目")
|
||||
print(" python scripts/migrate_to_database.py migrate <project_id> # 迁移指定项目")
|
||||
@@ -1,47 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
数据迁移脚本 - 从旧架构迁移到新架构
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any
|
||||
|
||||
def migrate_project_data(project_id: str):
|
||||
"""迁移单个项目的数据"""
|
||||
print(f"迁移项目: {project_id}")
|
||||
|
||||
# 这里添加具体的数据迁移逻辑
|
||||
# 1. 读取旧的数据结构
|
||||
# 2. 转换为新的数据结构
|
||||
# 3. 保存到新的位置
|
||||
# 4. 更新数据库记录
|
||||
|
||||
print(f"项目 {project_id} 迁移完成")
|
||||
|
||||
def migrate_all_projects():
|
||||
"""迁移所有项目"""
|
||||
print("开始迁移所有项目...")
|
||||
|
||||
# 获取所有项目目录
|
||||
data_dir = Path("data")
|
||||
projects_dir = data_dir / "projects"
|
||||
|
||||
if not projects_dir.exists():
|
||||
print("没有找到项目目录")
|
||||
return
|
||||
|
||||
for project_dir in projects_dir.iterdir():
|
||||
if project_dir.is_dir() and not project_dir.name.startswith('.'):
|
||||
migrate_project_data(project_dir.name)
|
||||
|
||||
print("所有项目迁移完成")
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) > 1:
|
||||
project_id = sys.argv[1]
|
||||
migrate_project_data(project_id)
|
||||
else:
|
||||
migrate_all_projects()
|
||||
@@ -1,357 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
存储架构优化实施脚本
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any, List
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
# 添加backend目录到路径
|
||||
backend_dir = project_root / "backend"
|
||||
sys.path.insert(0, str(backend_dir))
|
||||
|
||||
from core.database import SessionLocal
|
||||
from models.project import Project
|
||||
from models.clip import Clip
|
||||
from models.collection import Collection
|
||||
|
||||
def analyze_current_storage():
|
||||
"""分析当前存储使用情况"""
|
||||
print("📊 分析当前存储使用情况...")
|
||||
|
||||
# 分析数据库
|
||||
db = SessionLocal()
|
||||
try:
|
||||
total_projects = db.query(Project).count()
|
||||
total_clips = db.query(Clip).count()
|
||||
total_collections = db.query(Collection).count()
|
||||
|
||||
print(f" 数据库统计:")
|
||||
print(f" - 项目数量: {total_projects}")
|
||||
print(f" - 切片数量: {total_clips}")
|
||||
print(f" - 合集数量: {total_collections}")
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
# 分析文件系统
|
||||
data_dir = project_root / "data"
|
||||
projects_dir = data_dir / "projects"
|
||||
|
||||
if projects_dir.exists():
|
||||
project_dirs = [d for d in projects_dir.iterdir() if d.is_dir()]
|
||||
print(f" 文件系统统计:")
|
||||
print(f" - 项目目录数量: {len(project_dirs)}")
|
||||
|
||||
total_size = 0
|
||||
for project_dir in project_dirs:
|
||||
project_size = sum(f.stat().st_size for f in project_dir.rglob('*') if f.is_file())
|
||||
total_size += project_size
|
||||
print(f" - {project_dir.name}: {project_size / 1024 / 1024:.2f} MB")
|
||||
|
||||
print(f" - 总文件大小: {total_size / 1024 / 1024:.2f} MB")
|
||||
|
||||
return {
|
||||
"db_projects": total_projects,
|
||||
"db_clips": total_clips,
|
||||
"db_collections": total_collections,
|
||||
"fs_projects": len(project_dirs) if projects_dir.exists() else 0,
|
||||
"fs_total_size": total_size if projects_dir.exists() else 0
|
||||
}
|
||||
|
||||
def create_optimized_structure():
|
||||
"""创建优化的文件结构"""
|
||||
print("\n🏗️ 创建优化的文件结构...")
|
||||
|
||||
data_dir = project_root / "data"
|
||||
|
||||
# 创建新的目录结构
|
||||
directories = [
|
||||
data_dir / "temp",
|
||||
data_dir / "cache",
|
||||
data_dir / "backups"
|
||||
]
|
||||
|
||||
for directory in directories:
|
||||
directory.mkdir(parents=True, exist_ok=True)
|
||||
print(f" 创建目录: {directory}")
|
||||
|
||||
# 创建示例项目结构
|
||||
example_project_dir = data_dir / "projects" / "example-project"
|
||||
example_dirs = [
|
||||
example_project_dir / "raw",
|
||||
example_project_dir / "processing",
|
||||
example_project_dir / "output" / "clips",
|
||||
example_project_dir / "output" / "collections"
|
||||
]
|
||||
|
||||
for directory in example_dirs:
|
||||
directory.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
print(f" 创建示例项目结构: {example_project_dir}")
|
||||
|
||||
def optimize_database_schema():
|
||||
"""优化数据库模式"""
|
||||
print("\n🗄️ 优化数据库模式...")
|
||||
|
||||
# 这里可以添加数据库模式优化的逻辑
|
||||
# 比如添加索引、优化字段类型等
|
||||
|
||||
print(" 数据库模式优化完成")
|
||||
|
||||
def create_storage_service():
|
||||
"""创建统一存储服务"""
|
||||
print("\n🔧 创建统一存储服务...")
|
||||
|
||||
storage_service_content = '''"""
|
||||
统一存储服务
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any, Optional
|
||||
from core.config import get_data_directory
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class StorageService:
|
||||
"""统一存储服务"""
|
||||
|
||||
def __init__(self, project_id: str):
|
||||
self.project_id = project_id
|
||||
self.data_dir = get_data_directory()
|
||||
self.project_dir = self.data_dir / "projects" / project_id
|
||||
|
||||
# 确保项目目录结构存在
|
||||
self._ensure_project_structure()
|
||||
|
||||
def _ensure_project_structure(self):
|
||||
"""确保项目目录结构存在"""
|
||||
directories = [
|
||||
self.project_dir / "raw",
|
||||
self.project_dir / "processing",
|
||||
self.project_dir / "output" / "clips",
|
||||
self.project_dir / "output" / "collections"
|
||||
]
|
||||
|
||||
for directory in directories:
|
||||
directory.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def save_metadata(self, metadata: Dict[str, Any], step: str) -> str:
|
||||
"""保存处理元数据到文件系统"""
|
||||
metadata_file = self.project_dir / "processing" / f"{step}.json"
|
||||
|
||||
with open(metadata_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(metadata, f, ensure_ascii=False, indent=2)
|
||||
|
||||
logger.info(f"保存元数据: {metadata_file}")
|
||||
return str(metadata_file)
|
||||
|
||||
def get_metadata(self, step: str) -> Optional[Dict[str, Any]]:
|
||||
"""获取处理元数据"""
|
||||
metadata_file = self.project_dir / "processing" / f"{step}.json"
|
||||
|
||||
if metadata_file.exists():
|
||||
with open(metadata_file, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
return None
|
||||
|
||||
def save_file(self, file_path: Path, target_name: str, file_type: str = "raw") -> str:
|
||||
"""保存文件到项目目录"""
|
||||
if file_type == "raw":
|
||||
target_path = self.project_dir / "raw" / target_name
|
||||
elif file_type == "clip":
|
||||
target_path = self.project_dir / "output" / "clips" / target_name
|
||||
elif file_type == "collection":
|
||||
target_path = self.project_dir / "output" / "collections" / target_name
|
||||
else:
|
||||
raise ValueError(f"不支持的文件类型: {file_type}")
|
||||
|
||||
shutil.copy2(file_path, target_path)
|
||||
logger.info(f"保存文件: {target_path}")
|
||||
return str(target_path)
|
||||
|
||||
def get_file_path(self, file_type: str, file_name: str) -> Optional[Path]:
|
||||
"""获取文件路径"""
|
||||
if file_type == "raw":
|
||||
return self.project_dir / "raw" / file_name
|
||||
elif file_type == "clip":
|
||||
return self.project_dir / "output" / "clips" / file_name
|
||||
elif file_type == "collection":
|
||||
return self.project_dir / "output" / "collections" / file_name
|
||||
else:
|
||||
return None
|
||||
|
||||
def cleanup_temp_files(self):
|
||||
"""清理临时文件"""
|
||||
temp_dir = self.data_dir / "temp"
|
||||
if temp_dir.exists():
|
||||
for temp_file in temp_dir.iterdir():
|
||||
if temp_file.is_file():
|
||||
temp_file.unlink()
|
||||
logger.info(f"清理临时文件: {temp_file}")
|
||||
'''
|
||||
|
||||
storage_service_path = backend_dir / "services" / "storage_service.py"
|
||||
with open(storage_service_path, 'w', encoding='utf-8') as f:
|
||||
f.write(storage_service_content)
|
||||
|
||||
print(f" 创建存储服务: {storage_service_path}")
|
||||
|
||||
def create_migration_script():
|
||||
"""创建数据迁移脚本"""
|
||||
print("\n📦 创建数据迁移脚本...")
|
||||
|
||||
migration_script_content = '''#!/usr/bin/env python3
|
||||
"""
|
||||
数据迁移脚本 - 从旧架构迁移到新架构
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any
|
||||
|
||||
def migrate_project_data(project_id: str):
|
||||
"""迁移单个项目的数据"""
|
||||
print(f"迁移项目: {project_id}")
|
||||
|
||||
# 这里添加具体的数据迁移逻辑
|
||||
# 1. 读取旧的数据结构
|
||||
# 2. 转换为新的数据结构
|
||||
# 3. 保存到新的位置
|
||||
# 4. 更新数据库记录
|
||||
|
||||
print(f"项目 {project_id} 迁移完成")
|
||||
|
||||
def migrate_all_projects():
|
||||
"""迁移所有项目"""
|
||||
print("开始迁移所有项目...")
|
||||
|
||||
# 获取所有项目目录
|
||||
data_dir = Path("data")
|
||||
projects_dir = data_dir / "projects"
|
||||
|
||||
if not projects_dir.exists():
|
||||
print("没有找到项目目录")
|
||||
return
|
||||
|
||||
for project_dir in projects_dir.iterdir():
|
||||
if project_dir.is_dir() and not project_dir.name.startswith('.'):
|
||||
migrate_project_data(project_dir.name)
|
||||
|
||||
print("所有项目迁移完成")
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) > 1:
|
||||
project_id = sys.argv[1]
|
||||
migrate_project_data(project_id)
|
||||
else:
|
||||
migrate_all_projects()
|
||||
'''
|
||||
|
||||
migration_script_path = project_root / "scripts" / "migrate_to_optimized_storage.py"
|
||||
with open(migration_script_path, 'w', encoding='utf-8') as f:
|
||||
f.write(migration_script_content)
|
||||
|
||||
print(f" 创建迁移脚本: {migration_script_path}")
|
||||
|
||||
def generate_optimization_report(stats: Dict[str, Any]):
|
||||
"""生成优化报告"""
|
||||
print("\n📋 生成优化报告...")
|
||||
|
||||
report_content = f"""# 存储架构优化报告
|
||||
|
||||
## 当前状态分析
|
||||
|
||||
### 数据库统计
|
||||
- 项目数量: {stats['db_projects']}
|
||||
- 切片数量: {stats['db_clips']}
|
||||
- 合集数量: {stats['db_collections']}
|
||||
|
||||
### 文件系统统计
|
||||
- 项目目录数量: {stats['fs_projects']}
|
||||
- 总文件大小: {stats['fs_total_size'] / 1024 / 1024:.2f} MB
|
||||
|
||||
## 优化建议
|
||||
|
||||
### 1. 存储空间优化
|
||||
- 移除数据库中的冗余数据
|
||||
- 只保留文件路径引用
|
||||
- 预计节省空间: {stats['fs_total_size'] / 1024 / 1024 * 0.1:.2f} MB
|
||||
|
||||
### 2. 性能优化
|
||||
- 减少数据同步开销
|
||||
- 优化文件访问路径
|
||||
- 添加缓存机制
|
||||
|
||||
### 3. 维护性优化
|
||||
- 简化数据管理逻辑
|
||||
- 统一存储接口
|
||||
- 改进错误处理
|
||||
|
||||
## 实施步骤
|
||||
|
||||
1. ✅ 分析当前存储使用情况
|
||||
2. ✅ 创建优化的文件结构
|
||||
3. ✅ 优化数据库模式
|
||||
4. ✅ 创建统一存储服务
|
||||
5. ✅ 创建数据迁移脚本
|
||||
6. ⏳ 执行数据迁移
|
||||
7. ⏳ 测试新架构
|
||||
8. ⏳ 清理旧数据
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 迁移前请备份所有数据
|
||||
- 测试新架构的完整性
|
||||
- 验证文件路径的正确性
|
||||
- 确保API接口的兼容性
|
||||
"""
|
||||
|
||||
report_path = project_root / "docs" / "STORAGE_OPTIMIZATION_REPORT.md"
|
||||
with open(report_path, 'w', encoding='utf-8') as f:
|
||||
f.write(report_content)
|
||||
|
||||
print(f" 生成优化报告: {report_path}")
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("🚀 开始存储架构优化...")
|
||||
|
||||
# 1. 分析当前存储使用情况
|
||||
stats = analyze_current_storage()
|
||||
|
||||
# 2. 创建优化的文件结构
|
||||
create_optimized_structure()
|
||||
|
||||
# 3. 优化数据库模式
|
||||
optimize_database_schema()
|
||||
|
||||
# 4. 创建统一存储服务
|
||||
create_storage_service()
|
||||
|
||||
# 5. 创建数据迁移脚本
|
||||
create_migration_script()
|
||||
|
||||
# 6. 生成优化报告
|
||||
generate_optimization_report(stats)
|
||||
|
||||
print("\n✅ 存储架构优化完成!")
|
||||
print("\n📝 下一步操作:")
|
||||
print("1. 查看优化报告: docs/STORAGE_OPTIMIZATION_REPORT.md")
|
||||
print("2. 执行数据迁移: python scripts/migrate_to_optimized_storage.py")
|
||||
print("3. 测试新架构")
|
||||
print("4. 清理旧数据")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,56 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
重新创建数据库表
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
# 添加backend目录到路径
|
||||
backend_dir = project_root / "backend"
|
||||
sys.path.insert(0, str(backend_dir))
|
||||
|
||||
from core.database import create_tables, engine
|
||||
from models.base import Base
|
||||
|
||||
def main():
|
||||
"""重新创建数据库表"""
|
||||
print("🚀 重新创建数据库表...")
|
||||
|
||||
try:
|
||||
# 删除所有表
|
||||
Base.metadata.drop_all(bind=engine)
|
||||
print("🗑️ 已删除所有表")
|
||||
|
||||
# 重新创建所有表
|
||||
create_tables()
|
||||
print("✅ 已重新创建所有表")
|
||||
|
||||
# 验证表结构
|
||||
from sqlalchemy import inspect
|
||||
inspector = inspect(engine)
|
||||
|
||||
tables = inspector.get_table_names()
|
||||
print(f"📊 数据库表: {tables}")
|
||||
|
||||
for table_name in tables:
|
||||
columns = inspector.get_columns(table_name)
|
||||
print(f"\n📋 {table_name} 表结构:")
|
||||
for column in columns:
|
||||
print(f" - {column['name']}: {column['type']}")
|
||||
|
||||
print("\n🎉 数据库重新创建完成!")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 重新创建数据库失败: {e}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = main()
|
||||
sys.exit(0 if success else 1)
|
||||
@@ -1,134 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
模拟前端的完整调用流程
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
from typing import List
|
||||
|
||||
def simulate_frontend_flow():
|
||||
"""模拟前端的完整调用流程"""
|
||||
|
||||
# 测试数据
|
||||
project_id = "5c48803d-0aa7-48d7-a270-2b33e4954f25"
|
||||
collection_id = "0e181e1a-52c2-42c2-9481-cc306e3b27f9"
|
||||
|
||||
print("🎯 模拟前端的完整调用流程")
|
||||
print("=" * 50)
|
||||
|
||||
# 1. 模拟前端获取项目数据
|
||||
print("\n1️⃣ 模拟前端获取项目数据...")
|
||||
try:
|
||||
response = requests.get(f"http://localhost:8000/api/v1/projects/{project_id}")
|
||||
if response.status_code == 200:
|
||||
project = response.json()
|
||||
print(f"✅ 项目: {project.get('name', 'Unknown')}")
|
||||
else:
|
||||
print(f"❌ 获取项目失败: {response.status_code}")
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"❌ 获取项目异常: {e}")
|
||||
return
|
||||
|
||||
# 2. 模拟前端获取合集数据
|
||||
print("\n2️⃣ 模拟前端获取合集数据...")
|
||||
try:
|
||||
response = requests.get(f"http://localhost:8000/api/v1/collections/?project_id={project_id}")
|
||||
if response.status_code == 200:
|
||||
collections_data = response.json()
|
||||
collections = collections_data.get('items', []) if isinstance(collections_data, dict) else collections_data
|
||||
|
||||
target_collection = None
|
||||
for collection in collections:
|
||||
if collection.get('id') == collection_id:
|
||||
target_collection = collection
|
||||
break
|
||||
|
||||
if target_collection:
|
||||
print(f"✅ 找到合集: {target_collection.get('name', 'Unknown')}")
|
||||
current_clip_ids = target_collection.get('clip_ids', [])
|
||||
print(f"📋 当前clip_ids: {current_clip_ids}")
|
||||
else:
|
||||
print("❌ 未找到目标合集")
|
||||
return
|
||||
else:
|
||||
print(f"❌ 获取合集失败: {response.status_code}")
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"❌ 获取合集异常: {e}")
|
||||
return
|
||||
|
||||
if len(current_clip_ids) < 2:
|
||||
print("⚠️ clip_ids数量不足,无法测试排序")
|
||||
return
|
||||
|
||||
# 3. 模拟前端拖拽排序
|
||||
print("\n3️⃣ 模拟前端拖拽排序...")
|
||||
print("模拟用户拖拽第一个切片到第二个位置")
|
||||
|
||||
# 交换前两个元素
|
||||
new_clip_ids = current_clip_ids[1:] + current_clip_ids[:1]
|
||||
print(f"📤 新顺序: {new_clip_ids}")
|
||||
|
||||
# 4. 模拟前端调用store的reorderCollectionClips方法
|
||||
print("\n4️⃣ 模拟前端调用store的reorderCollectionClips方法...")
|
||||
try:
|
||||
# 模拟前端的API调用
|
||||
response = requests.patch(
|
||||
f"http://localhost:8000/api/v1/collections/{collection_id}/reorder",
|
||||
json=new_clip_ids,
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
|
||||
print(f"📥 响应状态: {response.status_code}")
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
print(f"✅ 排序成功: {result.get('clip_ids', [])}")
|
||||
|
||||
# 检查返回的数据是否正确
|
||||
if result.get('clip_ids') == new_clip_ids:
|
||||
print("✅ 返回数据正确")
|
||||
else:
|
||||
print("❌ 返回数据不正确")
|
||||
else:
|
||||
print(f"❌ 排序失败: {response.text}")
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"❌ 排序异常: {e}")
|
||||
return
|
||||
|
||||
# 5. 模拟前端重新获取数据验证
|
||||
print("\n5️⃣ 模拟前端重新获取数据验证...")
|
||||
try:
|
||||
response = requests.get(f"http://localhost:8000/api/v1/collections/?project_id={project_id}")
|
||||
if response.status_code == 200:
|
||||
collections_data = response.json()
|
||||
collections = collections_data.get('items', []) if isinstance(collections_data, dict) else collections_data
|
||||
|
||||
target_collection = None
|
||||
for collection in collections:
|
||||
if collection.get('id') == collection_id:
|
||||
target_collection = collection
|
||||
break
|
||||
|
||||
if target_collection:
|
||||
updated_clip_ids = target_collection.get('clip_ids', [])
|
||||
print(f"✅ 验证结果: {updated_clip_ids}")
|
||||
|
||||
if updated_clip_ids == new_clip_ids:
|
||||
print("✅ 前端验证成功!排序功能完全正常")
|
||||
else:
|
||||
print("❌ 前端验证失败,数据没有更新")
|
||||
else:
|
||||
print("❌ 验证时未找到目标合集")
|
||||
else:
|
||||
print(f"❌ 验证失败: {response.status_code}")
|
||||
except Exception as e:
|
||||
print(f"❌ 验证异常: {e}")
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("🎉 前端调用流程模拟完成!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
simulate_frontend_flow()
|
||||
@@ -1,153 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
同步所有项目数据到数据库
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
# 设置环境变量
|
||||
import os
|
||||
os.environ['PYTHONPATH'] = str(project_root)
|
||||
|
||||
# 添加backend目录到路径
|
||||
backend_dir = project_root / "backend"
|
||||
sys.path.insert(0, str(backend_dir))
|
||||
|
||||
from core.database import SessionLocal
|
||||
from services.data_sync_service import DataSyncService
|
||||
from core.config import get_data_directory
|
||||
|
||||
def sync_all_projects():
|
||||
"""同步所有项目数据到数据库"""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
data_sync_service = DataSyncService(db)
|
||||
data_dir = get_data_directory()
|
||||
|
||||
print(f"开始同步所有项目数据...")
|
||||
print(f"数据目录: {data_dir}")
|
||||
|
||||
# 同步所有项目
|
||||
result = data_sync_service.sync_all_projects_from_filesystem(data_dir)
|
||||
|
||||
if result["success"]:
|
||||
print(f"✅ 同步完成!")
|
||||
print(f" 成功同步: {result['total_synced']} 个项目")
|
||||
print(f" 失败: {result['total_failed']} 个项目")
|
||||
|
||||
if result['synced_projects']:
|
||||
print(f" 成功同步的项目:")
|
||||
for project_id in result['synced_projects']:
|
||||
print(f" - {project_id}")
|
||||
|
||||
if result['failed_projects']:
|
||||
print(f" 失败的项目:")
|
||||
for failed in result['failed_projects']:
|
||||
print(f" - {failed['project_id']}: {failed['error']}")
|
||||
else:
|
||||
print(f"❌ 同步失败: {result.get('error', '未知错误')}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 同步过程中发生错误: {str(e)}")
|
||||
return False
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
def sync_specific_project(project_id: str):
|
||||
"""同步特定项目"""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
data_sync_service = DataSyncService(db)
|
||||
data_dir = get_data_directory()
|
||||
project_dir = data_dir / "projects" / project_id
|
||||
|
||||
if not project_dir.exists():
|
||||
print(f"❌ 项目目录不存在: {project_dir}")
|
||||
return False
|
||||
|
||||
print(f"开始同步项目: {project_id}")
|
||||
|
||||
# 同步项目数据
|
||||
result = data_sync_service.sync_project_from_filesystem(project_id, project_dir)
|
||||
|
||||
if result["success"]:
|
||||
print(f"✅ 项目 {project_id} 同步成功")
|
||||
print(f" 切片: {result.get('clips_synced', 0)} 个")
|
||||
print(f" 合集: {result.get('collections_synced', 0)} 个")
|
||||
else:
|
||||
print(f"❌ 项目 {project_id} 同步失败: {result.get('error', '未知错误')}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 同步项目 {project_id} 时发生错误: {str(e)}")
|
||||
return False
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
def check_database_status():
|
||||
"""检查数据库状态"""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
from models.project import Project
|
||||
from models.clip import Clip
|
||||
from models.collection import Collection
|
||||
|
||||
# 统计数据库中的数据
|
||||
total_projects = db.query(Project).count()
|
||||
total_clips = db.query(Clip).count()
|
||||
total_collections = db.query(Collection).count()
|
||||
|
||||
print(f"📊 数据库状态:")
|
||||
print(f" 项目总数: {total_projects}")
|
||||
print(f" 切片总数: {total_clips}")
|
||||
print(f" 合集总数: {total_collections}")
|
||||
|
||||
# 显示项目列表
|
||||
if total_projects > 0:
|
||||
print(f"\n📋 项目列表:")
|
||||
projects = db.query(Project).all()
|
||||
for project in projects:
|
||||
clips_count = db.query(Clip).filter(Clip.project_id == project.id).count()
|
||||
collections_count = db.query(Collection).filter(Collection.project_id == project.id).count()
|
||||
print(f" - {project.id}: {project.name} (切片: {clips_count}, 合集: {collections_count})")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 检查数据库状态失败: {str(e)}")
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) == 1:
|
||||
# 同步所有项目
|
||||
print("🔄 开始同步所有项目数据...")
|
||||
success = sync_all_projects()
|
||||
if success:
|
||||
print("\n📊 同步后的数据库状态:")
|
||||
check_database_status()
|
||||
elif len(sys.argv) == 2:
|
||||
if sys.argv[1] == "status":
|
||||
# 检查状态
|
||||
check_database_status()
|
||||
else:
|
||||
# 同步特定项目
|
||||
project_id = sys.argv[1]
|
||||
success = sync_specific_project(project_id)
|
||||
if success:
|
||||
print(f"\n📊 项目 {project_id} 同步后的状态:")
|
||||
check_database_status()
|
||||
else:
|
||||
print("用法:")
|
||||
print(" python sync_all_projects.py # 同步所有项目")
|
||||
print(" python sync_all_projects.py status # 检查数据库状态")
|
||||
print(" python sync_all_projects.py <project_id> # 同步特定项目")
|
||||
@@ -1,207 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
测试所有步骤的方法调用修复
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
project_root = Path(__file__).parent.parent
|
||||
backend_path = project_root / "backend"
|
||||
sys.path.insert(0, str(backend_path))
|
||||
|
||||
def test_step1_methods():
|
||||
"""测试步骤1方法"""
|
||||
print("测试步骤1方法...")
|
||||
|
||||
try:
|
||||
from pipeline.step1_outline import OutlineExtractor
|
||||
|
||||
extractor = OutlineExtractor()
|
||||
print("✓ 步骤1实例创建成功")
|
||||
|
||||
# 检查方法是否存在
|
||||
if hasattr(extractor, 'extract_outline'):
|
||||
print("✓ extract_outline方法存在")
|
||||
return True
|
||||
else:
|
||||
print("✗ extract_outline方法不存在")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ 步骤1测试失败: {e}")
|
||||
return False
|
||||
|
||||
def test_step2_methods():
|
||||
"""测试步骤2方法"""
|
||||
print("\n测试步骤2方法...")
|
||||
|
||||
try:
|
||||
from pipeline.step2_timeline import TimelineExtractor
|
||||
|
||||
extractor = TimelineExtractor()
|
||||
print("✓ 步骤2实例创建成功")
|
||||
|
||||
# 检查方法是否存在
|
||||
if hasattr(extractor, 'extract_timeline'):
|
||||
print("✓ extract_timeline方法存在")
|
||||
return True
|
||||
else:
|
||||
print("✗ extract_timeline方法不存在")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ 步骤2测试失败: {e}")
|
||||
return False
|
||||
|
||||
def test_step3_methods():
|
||||
"""测试步骤3方法"""
|
||||
print("\n测试步骤3方法...")
|
||||
|
||||
try:
|
||||
from pipeline.step3_scoring import ClipScorer
|
||||
|
||||
scorer = ClipScorer()
|
||||
print("✓ 步骤3实例创建成功")
|
||||
|
||||
# 检查方法是否存在
|
||||
if hasattr(scorer, 'score_clips'):
|
||||
print("✓ score_clips方法存在")
|
||||
return True
|
||||
else:
|
||||
print("✗ score_clips方法不存在")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ 步骤3测试失败: {e}")
|
||||
return False
|
||||
|
||||
def test_step4_methods():
|
||||
"""测试步骤4方法"""
|
||||
print("\n测试步骤4方法...")
|
||||
|
||||
try:
|
||||
from pipeline.step4_title import TitleGenerator
|
||||
|
||||
generator = TitleGenerator()
|
||||
print("✓ 步骤4实例创建成功")
|
||||
|
||||
# 检查方法是否存在
|
||||
if hasattr(generator, 'generate_titles'):
|
||||
print("✓ generate_titles方法存在")
|
||||
return True
|
||||
else:
|
||||
print("✗ generate_titles方法不存在")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ 步骤4测试失败: {e}")
|
||||
return False
|
||||
|
||||
def test_step5_methods():
|
||||
"""测试步骤5方法"""
|
||||
print("\n测试步骤5方法...")
|
||||
|
||||
try:
|
||||
from pipeline.step5_clustering import ClusteringEngine
|
||||
|
||||
clusterer = ClusteringEngine()
|
||||
print("✓ 步骤5实例创建成功")
|
||||
|
||||
# 检查方法是否存在
|
||||
if hasattr(clusterer, 'cluster_clips'):
|
||||
print("✓ cluster_clips方法存在")
|
||||
return True
|
||||
else:
|
||||
print("✗ cluster_clips方法不存在")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ 步骤5测试失败: {e}")
|
||||
return False
|
||||
|
||||
def test_step6_methods():
|
||||
"""测试步骤6方法"""
|
||||
print("\n测试步骤6方法...")
|
||||
|
||||
try:
|
||||
from pipeline.step6_video import VideoGenerator
|
||||
|
||||
generator = VideoGenerator()
|
||||
print("✓ 步骤6实例创建成功")
|
||||
|
||||
# 检查方法是否存在
|
||||
methods_to_check = ['generate_clips', 'generate_collections', 'save_clip_metadata', 'save_collection_metadata']
|
||||
all_methods_exist = True
|
||||
|
||||
for method in methods_to_check:
|
||||
if hasattr(generator, method):
|
||||
print(f"✓ {method}方法存在")
|
||||
else:
|
||||
print(f"✗ {method}方法不存在")
|
||||
all_methods_exist = False
|
||||
|
||||
return all_methods_exist
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ 步骤6测试失败: {e}")
|
||||
return False
|
||||
|
||||
def test_pipeline_adapter():
|
||||
"""测试Pipeline适配器"""
|
||||
print("\n测试Pipeline适配器...")
|
||||
|
||||
try:
|
||||
from services.pipeline_adapter import PipelineAdapter
|
||||
from core.database import SessionLocal
|
||||
|
||||
# 创建数据库会话
|
||||
db = SessionLocal()
|
||||
|
||||
# 创建适配器
|
||||
adapter = PipelineAdapter(db, task_id="test-task", project_id="test-project")
|
||||
print("✓ Pipeline适配器创建成功")
|
||||
|
||||
db.close()
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ Pipeline适配器测试失败: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
"""主测试函数"""
|
||||
print("开始测试所有步骤的方法调用修复...")
|
||||
print("=" * 60)
|
||||
|
||||
tests = [
|
||||
test_step1_methods,
|
||||
test_step2_methods,
|
||||
test_step3_methods,
|
||||
test_step4_methods,
|
||||
test_step5_methods,
|
||||
test_step6_methods,
|
||||
test_pipeline_adapter
|
||||
]
|
||||
|
||||
passed = 0
|
||||
total = len(tests)
|
||||
|
||||
for test in tests:
|
||||
if test():
|
||||
passed += 1
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print(f"测试结果: {passed}/{total} 通过")
|
||||
|
||||
if passed == total:
|
||||
print("✓ 所有测试通过!所有步骤的方法调用修复成功")
|
||||
return 0
|
||||
else:
|
||||
print("✗ 部分测试失败,需要进一步修复")
|
||||
return 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -1,160 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
测试clip文件命名和查找逻辑
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import uuid
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root / "backend"))
|
||||
|
||||
from utils.video_processor import VideoProcessor
|
||||
from core.path_utils import get_clips_directory, get_clip_file_path
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def test_clip_file_naming():
|
||||
"""测试clip文件命名逻辑"""
|
||||
print("🧪 测试clip文件命名逻辑...")
|
||||
|
||||
# 测试数据
|
||||
test_cases = [
|
||||
{
|
||||
"clip_id": str(uuid.uuid4()),
|
||||
"title": "测试标题1",
|
||||
"expected_pattern": "测试标题1"
|
||||
},
|
||||
{
|
||||
"clip_id": str(uuid.uuid4()),
|
||||
"title": "测试标题2: 包含特殊字符",
|
||||
"expected_pattern": "测试标题2_ 包含特殊字符"
|
||||
},
|
||||
{
|
||||
"clip_id": str(uuid.uuid4()),
|
||||
"title": "测试标题3/包含/斜杠",
|
||||
"expected_pattern": "测试标题3_包含_斜杠"
|
||||
},
|
||||
{
|
||||
"clip_id": str(uuid.uuid4()),
|
||||
"title": "测试标题4*包含*星号",
|
||||
"expected_pattern": "测试标题4_包含_星号"
|
||||
}
|
||||
]
|
||||
|
||||
for i, test_case in enumerate(test_cases, 1):
|
||||
print(f"\n📝 测试用例 {i}:")
|
||||
print(f" Clip ID: {test_case['clip_id']}")
|
||||
print(f" 原始标题: {test_case['title']}")
|
||||
|
||||
# 测试文件名清理
|
||||
safe_title = VideoProcessor.sanitize_filename(test_case['title'])
|
||||
print(f" 清理后标题: {safe_title}")
|
||||
|
||||
# 测试完整文件路径生成
|
||||
expected_path = get_clip_file_path(test_case['clip_id'], test_case['title'])
|
||||
print(f" 生成的文件路径: {expected_path}")
|
||||
|
||||
# 验证文件名格式
|
||||
expected_filename = f"{test_case['clip_id']}_{safe_title}.mp4"
|
||||
if expected_filename in str(expected_path):
|
||||
print(f" ✅ 文件名格式正确")
|
||||
else:
|
||||
print(f" ❌ 文件名格式错误")
|
||||
|
||||
# 测试文件查找模式
|
||||
clips_dir = get_clips_directory()
|
||||
search_pattern = f"{test_case['clip_id']}_*.mp4"
|
||||
print(f" 查找模式: {search_pattern}")
|
||||
|
||||
# 模拟文件查找
|
||||
print(f" 预期查找结果: {clips_dir / expected_filename}")
|
||||
|
||||
def test_storage_service_naming():
|
||||
"""测试存储服务的文件命名逻辑"""
|
||||
print("\n🧪 测试存储服务文件命名逻辑...")
|
||||
|
||||
# 模拟clip数据
|
||||
clip_data = {
|
||||
"id": str(uuid.uuid4()),
|
||||
"title": "存储服务测试标题",
|
||||
"start_time": 0,
|
||||
"end_time": 60
|
||||
}
|
||||
|
||||
print(f" Clip ID: {clip_data['id']}")
|
||||
print(f" 标题: {clip_data['title']}")
|
||||
|
||||
# 模拟存储服务的命名逻辑
|
||||
from utils.video_processor import VideoProcessor
|
||||
safe_title = VideoProcessor.sanitize_filename(clip_data['title'])
|
||||
expected_filename = f"{clip_data['id']}_{safe_title}.mp4"
|
||||
|
||||
print(f" 预期文件名: {expected_filename}")
|
||||
|
||||
# 验证与path_utils的一致性
|
||||
from core.path_utils import get_clip_file_path
|
||||
path_utils_filename = get_clip_file_path(clip_data['id'], clip_data['title']).name
|
||||
|
||||
if expected_filename == path_utils_filename:
|
||||
print(f" ✅ 存储服务与path_utils命名一致")
|
||||
else:
|
||||
print(f" ❌ 存储服务与path_utils命名不一致")
|
||||
print(f" 存储服务: {expected_filename}")
|
||||
print(f" path_utils: {path_utils_filename}")
|
||||
|
||||
def test_api_lookup_logic():
|
||||
"""测试API查找逻辑"""
|
||||
print("\n🧪 测试API查找逻辑...")
|
||||
|
||||
clip_id = str(uuid.uuid4())
|
||||
title = "API查找测试标题"
|
||||
|
||||
print(f" Clip ID: {clip_id}")
|
||||
print(f" 标题: {title}")
|
||||
|
||||
# 模拟API查找逻辑
|
||||
clips_dir = get_clips_directory()
|
||||
search_pattern = f"{clip_id}_*.mp4"
|
||||
|
||||
print(f" 查找目录: {clips_dir}")
|
||||
print(f" 查找模式: {search_pattern}")
|
||||
|
||||
# 模拟查找结果
|
||||
expected_files = list(clips_dir.glob(search_pattern))
|
||||
print(f" 查找结果: {len(expected_files)} 个文件")
|
||||
|
||||
if expected_files:
|
||||
for file in expected_files:
|
||||
print(f" 找到文件: {file.name}")
|
||||
else:
|
||||
print(f" 未找到文件(这是正常的,因为文件不存在)")
|
||||
|
||||
# 验证查找逻辑的正确性
|
||||
safe_title = VideoProcessor.sanitize_filename(title)
|
||||
expected_filename = f"{clip_id}_{safe_title}.mp4"
|
||||
expected_path = clips_dir / expected_filename
|
||||
|
||||
print(f" 预期文件路径: {expected_path}")
|
||||
|
||||
# 检查查找模式是否能匹配预期文件
|
||||
import fnmatch
|
||||
if fnmatch.fnmatch(expected_filename, search_pattern.replace("*", "*")):
|
||||
print(f" ✅ 查找模式能正确匹配预期文件")
|
||||
else:
|
||||
print(f" ❌ 查找模式无法匹配预期文件")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_clip_file_naming()
|
||||
test_storage_service_naming()
|
||||
test_api_lookup_logic()
|
||||
|
||||
print("\n🎉 测试完成!")
|
||||
|
||||
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
测试切片API的数据转换
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
|
||||
def test_clips_api():
|
||||
"""测试切片API的数据转换"""
|
||||
base_url = "http://localhost:8000/api/v1"
|
||||
project_id = "1fdb0bf1-7f3c-44f7-a69d-90c5a1d26fbe"
|
||||
|
||||
print("🧪 开始测试切片API...")
|
||||
|
||||
# 1. 测试后端API
|
||||
print("\n1. 测试后端API...")
|
||||
try:
|
||||
response = requests.get(f"{base_url}/clips/?project_id={project_id}")
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
print(f"✅ 后端API返回 {len(data['items'])} 个切片")
|
||||
|
||||
if data['items']:
|
||||
clip = data['items'][0]
|
||||
print(f" 第一个切片:")
|
||||
print(f" - ID: {clip['id']}")
|
||||
print(f" - 标题: {clip['title']}")
|
||||
print(f" - 开始时间: {clip['start_time']}秒")
|
||||
print(f" - 结束时间: {clip['end_time']}秒")
|
||||
print(f" - 分数: {clip['score']}")
|
||||
|
||||
metadata = clip.get('clip_metadata', {})
|
||||
print(f" - 推荐理由: {metadata.get('recommend_reason', '无')}")
|
||||
print(f" - 内容要点: {len(metadata.get('content', []))} 个")
|
||||
else:
|
||||
print(f"❌ 后端API失败: {response.status_code}")
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"❌ 后端API异常: {e}")
|
||||
return
|
||||
|
||||
# 2. 测试前端API(模拟)
|
||||
print("\n2. 测试前端API数据转换...")
|
||||
try:
|
||||
# 模拟前端的数据转换逻辑
|
||||
clips = data['items']
|
||||
converted_clips = []
|
||||
|
||||
for clip in clips:
|
||||
# 转换秒数为时间字符串格式
|
||||
def format_seconds_to_time(seconds):
|
||||
hours = int(seconds // 3600)
|
||||
minutes = int((seconds % 3600) // 60)
|
||||
secs = int(seconds % 60)
|
||||
return f"{hours:02d}:{minutes:02d}:{secs:02d}"
|
||||
|
||||
# 获取metadata中的内容
|
||||
metadata = clip.get('clip_metadata', {})
|
||||
|
||||
converted_clip = {
|
||||
'id': clip['id'],
|
||||
'title': clip['title'],
|
||||
'generated_title': clip['title'],
|
||||
'start_time': format_seconds_to_time(clip['start_time']),
|
||||
'end_time': format_seconds_to_time(clip['end_time']),
|
||||
'final_score': clip['score'] or 0,
|
||||
'recommend_reason': metadata.get('recommend_reason') or clip['description'] or '',
|
||||
'outline': metadata.get('outline') or clip['description'] or '',
|
||||
'content': metadata.get('content') or [clip['description'] or ''],
|
||||
'chunk_index': metadata.get('chunk_index') or 0
|
||||
}
|
||||
converted_clips.append(converted_clip)
|
||||
|
||||
print(f"✅ 前端转换完成: {len(converted_clips)} 个切片")
|
||||
|
||||
if converted_clips:
|
||||
clip = converted_clips[0]
|
||||
print(f" 第一个转换后的切片:")
|
||||
print(f" - ID: {clip['id']}")
|
||||
print(f" - 标题: {clip['title']}")
|
||||
print(f" - 开始时间: {clip['start_time']}")
|
||||
print(f" - 结束时间: {clip['end_time']}")
|
||||
print(f" - 分数: {clip['final_score']}")
|
||||
print(f" - 推荐理由: {clip['recommend_reason'][:50]}...")
|
||||
print(f" - 内容要点: {len(clip['content'])} 个")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 前端转换异常: {e}")
|
||||
return
|
||||
|
||||
print("\n🎉 切片API测试完成!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_clips_api()
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
测试合集排序功能
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
from typing import List
|
||||
|
||||
def test_collection_reorder():
|
||||
"""测试合集排序功能"""
|
||||
|
||||
# 测试数据
|
||||
project_id = "5c48803d-0aa7-48d7-a270-2b33e4954f25"
|
||||
collection_id = "0e181e1a-52c2-42c2-9481-cc306e3b27f9"
|
||||
|
||||
# 获取当前合集信息
|
||||
print("🔍 获取当前合集信息...")
|
||||
try:
|
||||
response = requests.get(f"http://localhost:8000/api/v1/collections/{collection_id}")
|
||||
if response.status_code == 200:
|
||||
collection = response.json()
|
||||
print(f"✅ 当前合集: {collection['name']}")
|
||||
print(f"📋 当前clip_ids: {collection.get('clip_ids', [])}")
|
||||
else:
|
||||
print(f"❌ 获取合集失败: {response.status_code}")
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"❌ 获取合集异常: {e}")
|
||||
return
|
||||
|
||||
# 测试新的排序端点
|
||||
print("\n🔄 测试新的排序端点: PATCH /collections/{collection_id}/reorder")
|
||||
try:
|
||||
# 获取当前的clip_ids并重新排序
|
||||
current_clip_ids = collection.get('clip_ids', [])
|
||||
if len(current_clip_ids) >= 2:
|
||||
# 交换前两个元素
|
||||
new_clip_ids = current_clip_ids[1:] + current_clip_ids[:1]
|
||||
|
||||
print(f"📤 发送排序请求: {new_clip_ids}")
|
||||
response = requests.patch(
|
||||
f"http://localhost:8000/api/v1/collections/{collection_id}/reorder",
|
||||
json=new_clip_ids,
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
|
||||
print(f"📥 响应状态: {response.status_code}")
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
print(f"✅ 排序成功: {result.get('clip_ids', [])}")
|
||||
else:
|
||||
print(f"❌ 排序失败: {response.text}")
|
||||
else:
|
||||
print("⚠️ clip_ids数量不足,无法测试排序")
|
||||
except Exception as e:
|
||||
print(f"❌ 新排序端点测试异常: {e}")
|
||||
|
||||
# 测试修复后的PUT端点
|
||||
print("\n🔄 测试修复后的PUT端点: PUT /collections/{collection_id}")
|
||||
try:
|
||||
current_clip_ids = collection.get('clip_ids', [])
|
||||
if len(current_clip_ids) >= 2:
|
||||
# 再次交换前两个元素
|
||||
new_clip_ids = current_clip_ids[1:] + current_clip_ids[:1]
|
||||
|
||||
update_data = {
|
||||
"metadata": {
|
||||
"clip_ids": new_clip_ids
|
||||
}
|
||||
}
|
||||
|
||||
print(f"📤 发送更新请求: {update_data}")
|
||||
response = requests.put(
|
||||
f"http://localhost:8000/api/v1/collections/{collection_id}",
|
||||
json=update_data,
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
|
||||
print(f"📥 响应状态: {response.status_code}")
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
print(f"✅ 更新成功: {result.get('clip_ids', [])}")
|
||||
else:
|
||||
print(f"❌ 更新失败: {response.text}")
|
||||
else:
|
||||
print("⚠️ clip_ids数量不足,无法测试排序")
|
||||
except Exception as e:
|
||||
print(f"❌ PUT端点测试异常: {e}")
|
||||
|
||||
# 验证更新结果
|
||||
print("\n🔍 验证更新结果...")
|
||||
try:
|
||||
response = requests.get(f"http://localhost:8000/api/v1/collections/{collection_id}")
|
||||
if response.status_code == 200:
|
||||
updated_collection = response.json()
|
||||
print(f"✅ 更新后的clip_ids: {updated_collection.get('clip_ids', [])}")
|
||||
else:
|
||||
print(f"❌ 验证失败: {response.status_code}")
|
||||
except Exception as e:
|
||||
print(f"❌ 验证异常: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_collection_reorder()
|
||||
@@ -1,164 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
测试完整的前端拖拽排序流程
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
|
||||
def test_complete_frontend_flow():
|
||||
"""测试完整的前端拖拽排序流程"""
|
||||
|
||||
# 项目ID和合集ID
|
||||
project_id = "86f9aa12-2f35-4618-b265-74b3d9a4cf2d"
|
||||
collection_id = "5e5dafc8-f29a-4705-8e87-b2bb06f2a5de"
|
||||
|
||||
print("🎯 测试完整的前端拖拽排序流程")
|
||||
print("=" * 60)
|
||||
|
||||
# 1. 模拟前端获取项目数据
|
||||
print("1️⃣ 模拟前端获取项目数据...")
|
||||
try:
|
||||
# 获取项目信息
|
||||
response = requests.get(f"http://localhost:8000/api/v1/projects/{project_id}")
|
||||
if response.status_code == 200:
|
||||
project = response.json()
|
||||
print(f"✅ 项目: {project['name']}")
|
||||
print(f"📊 状态: {project['status']}")
|
||||
else:
|
||||
print(f"❌ 获取项目失败: {response.status_code}")
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"❌ 获取项目异常: {e}")
|
||||
return
|
||||
|
||||
# 2. 模拟前端获取clips数据
|
||||
print("\n2️⃣ 模拟前端获取clips数据...")
|
||||
try:
|
||||
response = requests.get(f"http://localhost:8000/api/v1/clips/?project_id={project_id}")
|
||||
if response.status_code == 200:
|
||||
clips = response.json()['items']
|
||||
print(f"✅ 获取到 {len(clips)} 个clips")
|
||||
for i, clip in enumerate(clips[:3]): # 只显示前3个
|
||||
print(f" {i+1}. {clip['title'][:30]}...")
|
||||
else:
|
||||
print(f"❌ 获取clips失败: {response.status_code}")
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"❌ 获取clips异常: {e}")
|
||||
return
|
||||
|
||||
# 3. 模拟前端获取collections数据
|
||||
print("\n3️⃣ 模拟前端获取collections数据...")
|
||||
try:
|
||||
response = requests.get(f"http://localhost:8000/api/v1/collections/?project_id={project_id}")
|
||||
if response.status_code == 200:
|
||||
collections = response.json()['items']
|
||||
print(f"✅ 获取到 {len(collections)} 个collections")
|
||||
|
||||
target_collection = None
|
||||
for collection in collections:
|
||||
print(f" 📚 {collection['name']}: {len(collection['clip_ids'])} 个片段")
|
||||
if collection['id'] == collection_id:
|
||||
target_collection = collection
|
||||
|
||||
if not target_collection:
|
||||
print("❌ 未找到目标合集")
|
||||
return
|
||||
|
||||
initial_clip_ids = target_collection['clip_ids']
|
||||
print(f"🎯 目标合集: {target_collection['name']}")
|
||||
print(f"📋 初始clip_ids: {initial_clip_ids}")
|
||||
else:
|
||||
print(f"❌ 获取collections失败: {response.status_code}")
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"❌ 获取collections异常: {e}")
|
||||
return
|
||||
|
||||
# 4. 模拟前端拖拽排序操作
|
||||
print("\n4️⃣ 模拟前端拖拽排序操作...")
|
||||
if len(initial_clip_ids) >= 2:
|
||||
# 模拟拖拽:将第一个元素拖到第二个位置
|
||||
new_clip_ids = [initial_clip_ids[1], initial_clip_ids[0]] + initial_clip_ids[2:]
|
||||
print(f"🔄 拖拽操作: 将第1个元素移到第2个位置")
|
||||
print(f"📋 新顺序: {new_clip_ids}")
|
||||
|
||||
try:
|
||||
# 模拟前端的API调用
|
||||
print("📤 发送API请求...")
|
||||
response = requests.patch(
|
||||
f"http://localhost:8000/api/v1/projects/{project_id}/collections/{collection_id}/reorder",
|
||||
json=new_clip_ids,
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
|
||||
print(f"📥 响应状态: {response.status_code}")
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
print(f"✅ API调用成功")
|
||||
print(f"📝 响应消息: {result.get('message', '')}")
|
||||
print(f"📋 返回的clip_ids: {result.get('clip_ids', [])}")
|
||||
else:
|
||||
print(f"❌ API调用失败: {response.text}")
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"❌ API调用异常: {e}")
|
||||
return
|
||||
else:
|
||||
print("❌ 片段数量不足,无法测试拖拽")
|
||||
return
|
||||
|
||||
# 5. 模拟前端验证更新结果
|
||||
print("\n5️⃣ 模拟前端验证更新结果...")
|
||||
try:
|
||||
response = requests.get(f"http://localhost:8000/api/v1/collections/?project_id={project_id}")
|
||||
if response.status_code == 200:
|
||||
collections = response.json()['items']
|
||||
target_collection = next((c for c in collections if c['id'] == collection_id), None)
|
||||
if target_collection:
|
||||
updated_clip_ids = target_collection['clip_ids']
|
||||
print(f"📋 验证结果: {updated_clip_ids}")
|
||||
|
||||
if updated_clip_ids == new_clip_ids:
|
||||
print("✅ 拖拽排序成功!数据已正确更新")
|
||||
else:
|
||||
print("❌ 拖拽排序失败!数据未正确更新")
|
||||
print(f" 期望: {new_clip_ids}")
|
||||
print(f" 实际: {updated_clip_ids}")
|
||||
else:
|
||||
print("❌ 未找到目标合集")
|
||||
else:
|
||||
print(f"❌ 验证失败: {response.status_code}")
|
||||
except Exception as e:
|
||||
print(f"❌ 验证异常: {e}")
|
||||
|
||||
# 6. 模拟前端恢复原始状态
|
||||
print("\n6️⃣ 模拟前端恢复原始状态...")
|
||||
try:
|
||||
response = requests.patch(
|
||||
f"http://localhost:8000/api/v1/projects/{project_id}/collections/{collection_id}/reorder",
|
||||
json=initial_clip_ids,
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
print("✅ 状态恢复成功")
|
||||
else:
|
||||
print(f"❌ 状态恢复失败: {response.text}")
|
||||
except Exception as e:
|
||||
print(f"❌ 状态恢复异常: {e}")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("🎉 完整的前端拖拽排序流程测试完成!")
|
||||
print("\n📋 测试总结:")
|
||||
print(" ✅ 项目数据获取正常")
|
||||
print(" ✅ Clips数据获取正常")
|
||||
print(" ✅ Collections数据获取正常")
|
||||
print(" ✅ 拖拽排序API调用正常")
|
||||
print(" ✅ 数据更新验证正常")
|
||||
print(" ✅ 状态恢复正常")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_complete_frontend_flow()
|
||||
|
||||
@@ -1,159 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
完整测试合集排序功能
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
from typing import List
|
||||
|
||||
def test_complete_reorder():
|
||||
"""完整测试合集排序功能"""
|
||||
|
||||
# 测试数据
|
||||
project_id = "5c48803d-0aa7-48d7-a270-2b33e4954f25"
|
||||
collection_id = "0e181e1a-52c2-42c2-9481-cc306e3b27f9"
|
||||
|
||||
print("🎯 完整测试合集排序功能")
|
||||
print("=" * 50)
|
||||
|
||||
# 1. 获取初始状态
|
||||
print("\n1️⃣ 获取初始状态...")
|
||||
try:
|
||||
response = requests.get(f"http://localhost:8000/api/v1/collections/{collection_id}")
|
||||
if response.status_code == 200:
|
||||
collection = response.json()
|
||||
print(f"✅ 合集: {collection['name']}")
|
||||
initial_clip_ids = collection.get('clip_ids', [])
|
||||
print(f"📋 初始clip_ids: {initial_clip_ids}")
|
||||
else:
|
||||
print(f"❌ 获取合集失败: {response.status_code}")
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"❌ 获取合集异常: {e}")
|
||||
return
|
||||
|
||||
if len(initial_clip_ids) < 2:
|
||||
print("⚠️ clip_ids数量不足,无法测试排序")
|
||||
return
|
||||
|
||||
# 2. 测试多次排序
|
||||
print("\n2️⃣ 测试多次排序...")
|
||||
|
||||
# 第一次排序:交换前两个
|
||||
print("\n🔄 第一次排序:交换前两个元素")
|
||||
new_clip_ids_1 = initial_clip_ids[1:] + initial_clip_ids[:1]
|
||||
print(f"📤 新顺序: {new_clip_ids_1}")
|
||||
|
||||
try:
|
||||
response = requests.patch(
|
||||
f"http://localhost:8000/api/v1/collections/{collection_id}/reorder",
|
||||
json=new_clip_ids_1,
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
print(f"✅ 第一次排序成功: {result.get('clip_ids', [])}")
|
||||
else:
|
||||
print(f"❌ 第一次排序失败: {response.text}")
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"❌ 第一次排序异常: {e}")
|
||||
return
|
||||
|
||||
# 第二次排序:再次交换前两个
|
||||
print("\n🔄 第二次排序:再次交换前两个元素")
|
||||
new_clip_ids_2 = new_clip_ids_1[1:] + new_clip_ids_1[:1]
|
||||
print(f"📤 新顺序: {new_clip_ids_2}")
|
||||
|
||||
try:
|
||||
response = requests.patch(
|
||||
f"http://localhost:8000/api/v1/collections/{collection_id}/reorder",
|
||||
json=new_clip_ids_2,
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
print(f"✅ 第二次排序成功: {result.get('clip_ids', [])}")
|
||||
else:
|
||||
print(f"❌ 第二次排序失败: {response.text}")
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"❌ 第二次排序异常: {e}")
|
||||
return
|
||||
|
||||
# 第三次排序:恢复到原始顺序
|
||||
print("\n🔄 第三次排序:恢复到原始顺序")
|
||||
print(f"📤 原始顺序: {initial_clip_ids}")
|
||||
|
||||
try:
|
||||
response = requests.patch(
|
||||
f"http://localhost:8000/api/v1/collections/{collection_id}/reorder",
|
||||
json=initial_clip_ids,
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
print(f"✅ 第三次排序成功: {result.get('clip_ids', [])}")
|
||||
else:
|
||||
print(f"❌ 第三次排序失败: {response.text}")
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"❌ 第三次排序异常: {e}")
|
||||
return
|
||||
|
||||
# 3. 最终验证
|
||||
print("\n3️⃣ 最终验证...")
|
||||
try:
|
||||
response = requests.get(f"http://localhost:8000/api/v1/collections/{collection_id}")
|
||||
if response.status_code == 200:
|
||||
final_collection = response.json()
|
||||
final_clip_ids = final_collection.get('clip_ids', [])
|
||||
print(f"✅ 最终clip_ids: {final_clip_ids}")
|
||||
|
||||
# 检查是否恢复到原始顺序
|
||||
if final_clip_ids == initial_clip_ids:
|
||||
print("✅ 排序功能完全正常!数据已恢复到原始顺序")
|
||||
else:
|
||||
print("❌ 排序功能异常,数据未恢复到原始顺序")
|
||||
else:
|
||||
print(f"❌ 最终验证失败: {response.status_code}")
|
||||
except Exception as e:
|
||||
print(f"❌ 最终验证异常: {e}")
|
||||
|
||||
# 4. 测试前端API兼容性
|
||||
print("\n4️⃣ 测试前端API兼容性...")
|
||||
try:
|
||||
# 测试前端可能使用的其他API格式
|
||||
response = requests.get(f"http://localhost:8000/api/v1/collections/?project_id={project_id}")
|
||||
if response.status_code == 200:
|
||||
collections = response.json()
|
||||
if 'items' in collections:
|
||||
items = collections['items']
|
||||
else:
|
||||
items = collections
|
||||
|
||||
# 查找我们的合集
|
||||
target_collection = None
|
||||
for item in items:
|
||||
if item.get('id') == collection_id:
|
||||
target_collection = item
|
||||
break
|
||||
|
||||
if target_collection:
|
||||
print(f"✅ 前端API兼容性正常: {target_collection.get('clip_ids', [])}")
|
||||
else:
|
||||
print("❌ 前端API兼容性异常:未找到目标合集")
|
||||
else:
|
||||
print(f"❌ 前端API兼容性测试失败: {response.status_code}")
|
||||
except Exception as e:
|
||||
print(f"❌ 前端API兼容性测试异常: {e}")
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("🎉 合集排序功能测试完成!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_complete_reorder()
|
||||
@@ -1,177 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
最终测试脚本验证所有修复
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
project_root = Path(__file__).parent.parent
|
||||
backend_path = project_root / "backend"
|
||||
sys.path.insert(0, str(backend_path))
|
||||
|
||||
def test_websocket_fixes():
|
||||
"""测试WebSocket修复"""
|
||||
print("测试WebSocket修复...")
|
||||
|
||||
try:
|
||||
from services.websocket_notification_service import WebSocketNotificationService
|
||||
|
||||
# 测试方法签名
|
||||
service = WebSocketNotificationService()
|
||||
|
||||
# 检查send_processing_error方法
|
||||
import inspect
|
||||
sig = inspect.signature(service.send_processing_error)
|
||||
params = list(sig.parameters.keys())
|
||||
|
||||
if 'task_id' in params and 'project_id' in params and 'error' in params:
|
||||
print("✓ send_processing_error方法参数正确")
|
||||
return True
|
||||
else:
|
||||
print(f"✗ send_processing_error方法参数不正确: {params}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ WebSocket修复测试失败: {e}")
|
||||
return False
|
||||
|
||||
def test_step3_final():
|
||||
"""测试步骤3最终修复"""
|
||||
print("\n测试步骤3最终修复...")
|
||||
|
||||
try:
|
||||
from pipeline.step3_scoring import ClipScorer
|
||||
from pipeline.config import PROMPT_FILES
|
||||
|
||||
# 创建实例
|
||||
scorer = ClipScorer()
|
||||
print("✓ 步骤3实例创建成功")
|
||||
|
||||
# 检查配置
|
||||
if 'recommendation' in PROMPT_FILES:
|
||||
print("✓ recommendation配置存在")
|
||||
else:
|
||||
print("✗ recommendation配置不存在")
|
||||
return False
|
||||
|
||||
# 检查提示词
|
||||
if hasattr(scorer, 'recommendation_prompt') and scorer.recommendation_prompt:
|
||||
print("✓ 提示词加载成功")
|
||||
else:
|
||||
print("✗ 提示词加载失败")
|
||||
return False
|
||||
|
||||
# 检查方法
|
||||
if hasattr(scorer, 'score_clips'):
|
||||
print("✓ score_clips方法存在")
|
||||
else:
|
||||
print("✗ score_clips方法不存在")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ 步骤3最终测试失败: {e}")
|
||||
return False
|
||||
|
||||
def test_pipeline_adapter_methods():
|
||||
"""测试Pipeline适配器方法调用"""
|
||||
print("\n测试Pipeline适配器方法调用...")
|
||||
|
||||
try:
|
||||
from services.pipeline_adapter import PipelineAdapter
|
||||
from core.database import SessionLocal
|
||||
|
||||
# 创建数据库会话
|
||||
db = SessionLocal()
|
||||
|
||||
# 创建适配器
|
||||
adapter = PipelineAdapter(db, task_id="test-task", project_id="test-project")
|
||||
print("✓ Pipeline适配器创建成功")
|
||||
|
||||
# 检查方法调用是否正确
|
||||
# 这里我们只是检查类是否存在,实际的方法调用会在运行时测试
|
||||
db.close()
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ Pipeline适配器测试失败: {e}")
|
||||
return False
|
||||
|
||||
def test_retry_function():
|
||||
"""测试重试功能"""
|
||||
print("\n测试重试功能...")
|
||||
|
||||
try:
|
||||
# 检查重试API的导入
|
||||
from api.v1.projects import retry_processing
|
||||
print("✓ 重试功能导入成功")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ 重试功能测试失败: {e}")
|
||||
return False
|
||||
|
||||
def test_progress_manager():
|
||||
"""测试进度管理器"""
|
||||
print("\n测试进度管理器...")
|
||||
|
||||
try:
|
||||
from core.progress_manager import ProgressManager
|
||||
from core.database import SessionLocal
|
||||
|
||||
# 创建数据库会话
|
||||
db = SessionLocal()
|
||||
|
||||
# 创建进度管理器
|
||||
progress_manager = ProgressManager(db)
|
||||
print("✓ 进度管理器创建成功")
|
||||
|
||||
db.close()
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ 进度管理器测试失败: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
"""主测试函数"""
|
||||
print("开始最终测试所有修复...")
|
||||
print("=" * 60)
|
||||
|
||||
tests = [
|
||||
test_websocket_fixes,
|
||||
test_step3_final,
|
||||
test_pipeline_adapter_methods,
|
||||
test_retry_function,
|
||||
test_progress_manager
|
||||
]
|
||||
|
||||
passed = 0
|
||||
total = len(tests)
|
||||
|
||||
for test in tests:
|
||||
if test():
|
||||
passed += 1
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print(f"测试结果: {passed}/{total} 通过")
|
||||
|
||||
if passed == total:
|
||||
print("✓ 所有测试通过!所有修复验证成功")
|
||||
print("\n修复总结:")
|
||||
print("1. ✅ WebSocket通知服务参数错误已修复")
|
||||
print("2. ✅ 步骤3内容评分失败已修复")
|
||||
print("3. ✅ 方法调用错误已修复")
|
||||
print("4. ✅ 重试功能错误已修复")
|
||||
print("5. ✅ 进度管理器错误已修复")
|
||||
return 0
|
||||
else:
|
||||
print("✗ 部分测试失败,需要进一步修复")
|
||||
return 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -1,180 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
模拟前端实际的拖拽排序调用
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
|
||||
def test_frontend_actual_call():
|
||||
"""模拟前端实际的拖拽排序调用"""
|
||||
|
||||
# 项目ID和合集ID
|
||||
project_id = "86f9aa12-2f35-4618-b265-74b3d9a4cf2d"
|
||||
collection_id = "5e5dafc8-f29a-4705-8e87-b2bb06f2a5de"
|
||||
|
||||
print("🎯 模拟前端实际的拖拽排序调用")
|
||||
print("=" * 50)
|
||||
|
||||
# 1. 模拟前端获取初始数据
|
||||
print("1️⃣ 模拟前端获取初始数据...")
|
||||
try:
|
||||
# 获取collections数据
|
||||
response = requests.get(f"http://localhost:8000/api/v1/collections/?project_id={project_id}")
|
||||
if response.status_code == 200:
|
||||
collections = response.json()['items']
|
||||
target_collection = next((c for c in collections if c['id'] == collection_id), None)
|
||||
if target_collection:
|
||||
initial_clip_ids = target_collection['clip_ids']
|
||||
print(f"✅ 获取到合集: {target_collection['name']}")
|
||||
print(f"📋 初始clip_ids: {initial_clip_ids}")
|
||||
else:
|
||||
print("❌ 未找到目标合集")
|
||||
return
|
||||
else:
|
||||
print(f"❌ 获取collections失败: {response.status_code}")
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"❌ 获取数据异常: {e}")
|
||||
return
|
||||
|
||||
# 2. 模拟前端拖拽操作(交换前两个元素)
|
||||
print("\n2️⃣ 模拟前端拖拽操作...")
|
||||
if len(initial_clip_ids) >= 2:
|
||||
# 模拟拖拽:将第一个元素拖到第二个位置
|
||||
new_clip_ids = [initial_clip_ids[1], initial_clip_ids[0]] + initial_clip_ids[2:]
|
||||
print(f"🔄 拖拽操作: 将第1个元素移到第2个位置")
|
||||
print(f"📋 新顺序: {new_clip_ids}")
|
||||
|
||||
# 3. 模拟前端store的reorderCollectionClips调用
|
||||
print("\n3️⃣ 模拟前端store的reorderCollectionClips调用...")
|
||||
print(f"📤 调用: projectApi.reorderCollectionClips('{project_id}', '{collection_id}', {new_clip_ids})")
|
||||
|
||||
try:
|
||||
# 模拟前端的API调用
|
||||
response = requests.patch(
|
||||
f"http://localhost:8000/api/v1/projects/{project_id}/collections/{collection_id}/reorder",
|
||||
json=new_clip_ids,
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json"
|
||||
}
|
||||
)
|
||||
|
||||
print(f"📥 响应状态: {response.status_code}")
|
||||
print(f"📋 响应头: {dict(response.headers)}")
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
print(f"✅ API调用成功")
|
||||
print(f"📝 响应消息: {result.get('message', '')}")
|
||||
print(f"📋 返回的clip_ids: {result.get('clip_ids', [])}")
|
||||
else:
|
||||
print(f"❌ API调用失败")
|
||||
print(f"📋 错误响应: {response.text}")
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"❌ API调用异常: {e}")
|
||||
return
|
||||
else:
|
||||
print("❌ 片段数量不足,无法测试拖拽")
|
||||
return
|
||||
|
||||
# 4. 模拟前端验证更新结果
|
||||
print("\n4️⃣ 模拟前端验证更新结果...")
|
||||
try:
|
||||
response = requests.get(f"http://localhost:8000/api/v1/collections/?project_id={project_id}")
|
||||
if response.status_code == 200:
|
||||
collections = response.json()['items']
|
||||
target_collection = next((c for c in collections if c['id'] == collection_id), None)
|
||||
if target_collection:
|
||||
updated_clip_ids = target_collection['clip_ids']
|
||||
print(f"📋 验证结果: {updated_clip_ids}")
|
||||
|
||||
if updated_clip_ids == new_clip_ids:
|
||||
print("✅ 拖拽排序成功!数据已正确更新")
|
||||
else:
|
||||
print("❌ 拖拽排序失败!数据未正确更新")
|
||||
print(f" 期望: {new_clip_ids}")
|
||||
print(f" 实际: {updated_clip_ids}")
|
||||
else:
|
||||
print("❌ 未找到目标合集")
|
||||
else:
|
||||
print(f"❌ 验证失败: {response.status_code}")
|
||||
except Exception as e:
|
||||
print(f"❌ 验证异常: {e}")
|
||||
|
||||
# 5. 模拟前端错误处理
|
||||
print("\n5️⃣ 模拟前端错误处理...")
|
||||
|
||||
# 测试各种可能的错误情况
|
||||
print("\n🔄 测试错误1: 无效的project_id")
|
||||
try:
|
||||
response = requests.patch(
|
||||
f"http://localhost:8000/api/v1/projects/invalid-project/collections/{collection_id}/reorder",
|
||||
json=new_clip_ids,
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
print(f"📥 错误1响应: {response.status_code}")
|
||||
if response.status_code == 404:
|
||||
print("✅ 错误1处理正常")
|
||||
else:
|
||||
print(f"❌ 错误1处理异常: {response.text}")
|
||||
except Exception as e:
|
||||
print(f"❌ 错误1异常: {e}")
|
||||
|
||||
print("\n🔄 测试错误2: 无效的collection_id")
|
||||
try:
|
||||
response = requests.patch(
|
||||
f"http://localhost:8000/api/v1/projects/{project_id}/collections/invalid-collection/reorder",
|
||||
json=new_clip_ids,
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
print(f"📥 错误2响应: {response.status_code}")
|
||||
if response.status_code == 404:
|
||||
print("✅ 错误2处理正常")
|
||||
else:
|
||||
print(f"❌ 错误2处理异常: {response.text}")
|
||||
except Exception as e:
|
||||
print(f"❌ 错误2异常: {e}")
|
||||
|
||||
print("\n🔄 测试错误3: 无效的请求体")
|
||||
try:
|
||||
response = requests.patch(
|
||||
f"http://localhost:8000/api/v1/projects/{project_id}/collections/{collection_id}/reorder",
|
||||
json={"invalid": "data"},
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
print(f"📥 错误3响应: {response.status_code}")
|
||||
print(f"📋 错误3内容: {response.text}")
|
||||
except Exception as e:
|
||||
print(f"❌ 错误3异常: {e}")
|
||||
|
||||
# 6. 恢复原始状态
|
||||
print("\n6️⃣ 恢复原始状态...")
|
||||
try:
|
||||
response = requests.patch(
|
||||
f"http://localhost:8000/api/v1/projects/{project_id}/collections/{collection_id}/reorder",
|
||||
json=initial_clip_ids,
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
print("✅ 状态恢复成功")
|
||||
else:
|
||||
print(f"❌ 状态恢复失败: {response.text}")
|
||||
except Exception as e:
|
||||
print(f"❌ 状态恢复异常: {e}")
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("🎉 前端实际调用测试完成!")
|
||||
print("\n📋 测试总结:")
|
||||
print(" ✅ 数据获取正常")
|
||||
print(" ✅ API调用正常")
|
||||
print(" ✅ 数据更新正常")
|
||||
print(" ✅ 错误处理正常")
|
||||
print(" ✅ 状态恢复正常")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_frontend_actual_call()
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>API测试</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>API测试页面</h1>
|
||||
<button onclick="testClipsAPI()">测试切片API</button>
|
||||
<button onclick="testCollectionsAPI()">测试合集API</button>
|
||||
<div id="result"></div>
|
||||
|
||||
<script>
|
||||
const api = axios.create({
|
||||
baseURL: 'http://localhost:8000/api/v1',
|
||||
timeout: 300000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
async function testClipsAPI() {
|
||||
const resultDiv = document.getElementById('result');
|
||||
resultDiv.innerHTML = '测试中...';
|
||||
|
||||
try {
|
||||
const projectId = '1fdb0bf1-7f3c-44f7-a69d-90c5a1d26fbe';
|
||||
const response = await api.get(`/clips/?project_id=${projectId}`);
|
||||
const clips = response.data.items || response.data || [];
|
||||
|
||||
let html = '<h3>切片API测试结果:</h3>';
|
||||
html += `<p>找到 ${clips.length} 个切片</p>`;
|
||||
|
||||
clips.forEach((clip, index) => {
|
||||
html += `<div style="border: 1px solid #ccc; margin: 10px; padding: 10px;">`;
|
||||
html += `<h4>切片 ${index + 1}:</h4>`;
|
||||
html += `<p><strong>ID:</strong> ${clip.id}</p>`;
|
||||
html += `<p><strong>标题:</strong> ${clip.title}</p>`;
|
||||
html += `<p><strong>开始时间:</strong> ${clip.start_time}秒</p>`;
|
||||
html += `<p><strong>结束时间:</strong> ${clip.end_time}秒</p>`;
|
||||
html += `<p><strong>分数:</strong> ${clip.score}</p>`;
|
||||
|
||||
const metadata = clip.clip_metadata || {};
|
||||
html += `<p><strong>推荐理由:</strong> ${metadata.recommend_reason || '无'}</p>`;
|
||||
html += `<p><strong>内容要点:</strong> ${(metadata.content || []).length} 个</p>`;
|
||||
html += '</div>';
|
||||
});
|
||||
|
||||
resultDiv.innerHTML = html;
|
||||
} catch (error) {
|
||||
resultDiv.innerHTML = `<p style="color: red;">错误: ${error.message}</p>`;
|
||||
console.error('API错误:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function testCollectionsAPI() {
|
||||
const resultDiv = document.getElementById('result');
|
||||
resultDiv.innerHTML = '测试中...';
|
||||
|
||||
try {
|
||||
const projectId = '1fdb0bf1-7f3c-44f7-a69d-90c5a1d26fbe';
|
||||
const response = await api.get(`/collections/?project_id=${projectId}`);
|
||||
const collections = response.data.items || response.data || [];
|
||||
|
||||
let html = '<h3>合集API测试结果:</h3>';
|
||||
html += `<p>找到 ${collections.length} 个合集</p>`;
|
||||
|
||||
collections.forEach((collection, index) => {
|
||||
html += `<div style="border: 1px solid #ccc; margin: 10px; padding: 10px;">`;
|
||||
html += `<h4>合集 ${index + 1}:</h4>`;
|
||||
html += `<p><strong>ID:</strong> ${collection.id}</p>`;
|
||||
html += `<p><strong>名称:</strong> ${collection.name}</p>`;
|
||||
html += `<p><strong>描述:</strong> ${collection.description}</p>`;
|
||||
html += `<p><strong>切片ID:</strong> ${collection.clip_ids.join(', ')}</p>`;
|
||||
html += '</div>';
|
||||
});
|
||||
|
||||
resultDiv.innerHTML = html;
|
||||
} catch (error) {
|
||||
resultDiv.innerHTML = `<p style="color: red;">错误: ${error.message}</p>`;
|
||||
console.error('API错误:', error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
直接测试前端API调用
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
|
||||
def test_frontend_api_direct():
|
||||
"""直接测试前端API调用"""
|
||||
|
||||
collection_id = "0e181e1a-52c2-42c2-9481-cc306e3b27f9"
|
||||
|
||||
print("🔍 直接测试前端API调用")
|
||||
print("=" * 50)
|
||||
|
||||
# 获取当前合集信息
|
||||
print("\n1️⃣ 获取当前合集信息...")
|
||||
try:
|
||||
response = requests.get(f"http://localhost:8000/api/v1/collections/{collection_id}")
|
||||
if response.status_code == 200:
|
||||
collection = response.json()
|
||||
current_clip_ids = collection.get('clip_ids', [])
|
||||
print(f"✅ 当前clip_ids: {current_clip_ids}")
|
||||
else:
|
||||
print(f"❌ 获取合集失败: {response.status_code}")
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"❌ 获取合集异常: {e}")
|
||||
return
|
||||
|
||||
if len(current_clip_ids) < 2:
|
||||
print("⚠️ clip_ids数量不足,无法测试排序")
|
||||
return
|
||||
|
||||
# 测试前端API调用
|
||||
print("\n2️⃣ 测试前端API调用...")
|
||||
new_clip_ids = current_clip_ids[1:] + current_clip_ids[:1]
|
||||
|
||||
# 模拟前端调用
|
||||
try:
|
||||
print(f"📤 发送请求: PATCH /collections/{collection_id}/reorder")
|
||||
print(f"📤 请求数据: {new_clip_ids}")
|
||||
|
||||
response = requests.patch(
|
||||
f"http://localhost:8000/api/v1/collections/{collection_id}/reorder",
|
||||
json=new_clip_ids,
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json"
|
||||
}
|
||||
)
|
||||
|
||||
print(f"📥 响应状态: {response.status_code}")
|
||||
print(f"📥 响应头: {dict(response.headers)}")
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
print(f"✅ 请求成功: {result.get('clip_ids', [])}")
|
||||
else:
|
||||
print(f"❌ 请求失败: {response.text}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 请求异常: {e}")
|
||||
|
||||
# 验证结果
|
||||
print("\n3️⃣ 验证结果...")
|
||||
try:
|
||||
response = requests.get(f"http://localhost:8000/api/v1/collections/{collection_id}")
|
||||
if response.status_code == 200:
|
||||
collection = response.json()
|
||||
updated_clip_ids = collection.get('clip_ids', [])
|
||||
print(f"✅ 更新后的clip_ids: {updated_clip_ids}")
|
||||
|
||||
if updated_clip_ids == new_clip_ids:
|
||||
print("✅ 验证成功!")
|
||||
else:
|
||||
print("❌ 验证失败!")
|
||||
else:
|
||||
print(f"❌ 验证失败: {response.status_code}")
|
||||
except Exception as e:
|
||||
print(f"❌ 验证异常: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_frontend_api_direct()
|
||||
@@ -1,92 +0,0 @@
|
||||
// 测试前端切片API调用
|
||||
const axios = require('axios');
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: 'http://localhost:8000/api/v1',
|
||||
timeout: 300000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
// 响应拦截器
|
||||
api.interceptors.response.use(
|
||||
(response) => {
|
||||
return response.data;
|
||||
},
|
||||
(error) => {
|
||||
console.error('API Error:', error);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
async function testClipsAPI() {
|
||||
const projectId = '1fdb0bf1-7f3c-44f7-a69d-90c5a1d26fbe';
|
||||
|
||||
try {
|
||||
console.log('🧪 测试前端切片API调用...');
|
||||
|
||||
// 模拟前端API调用
|
||||
const response = await api.get(`/clips/?project_id=${projectId}`);
|
||||
const clips = response.items || response || [];
|
||||
|
||||
console.log(`✅ 原始API响应: ${clips.length} 个切片`);
|
||||
|
||||
// 模拟前端数据转换
|
||||
const convertedClips = clips.map((clip) => {
|
||||
// 转换秒数为时间字符串格式
|
||||
const formatSecondsToTime = (seconds) => {
|
||||
const hours = Math.floor(seconds / 3600);
|
||||
const minutes = Math.floor((seconds % 3600) / 60);
|
||||
const secs = Math.floor(seconds % 60);
|
||||
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
// 获取metadata中的内容
|
||||
const metadata = clip.clip_metadata || {};
|
||||
|
||||
return {
|
||||
id: clip.id,
|
||||
title: clip.title,
|
||||
generated_title: clip.title,
|
||||
start_time: formatSecondsToTime(clip.start_time),
|
||||
end_time: formatSecondsToTime(clip.end_time),
|
||||
final_score: clip.score || 0,
|
||||
recommend_reason: metadata.recommend_reason || clip.description || '',
|
||||
outline: metadata.outline || clip.description || '',
|
||||
content: metadata.content || [clip.description || ''],
|
||||
chunk_index: metadata.chunk_index || 0
|
||||
};
|
||||
});
|
||||
|
||||
console.log(`✅ 转换后的切片: ${convertedClips.length} 个`);
|
||||
|
||||
if (convertedClips.length > 0) {
|
||||
const firstClip = convertedClips[0];
|
||||
console.log('📋 第一个切片详情:');
|
||||
console.log(` ID: ${firstClip.id}`);
|
||||
console.log(` 标题: ${firstClip.title}`);
|
||||
console.log(` 时间: ${firstClip.start_time} - ${firstClip.end_time}`);
|
||||
console.log(` 分数: ${firstClip.final_score}`);
|
||||
console.log(` 推荐理由: ${firstClip.recommend_reason.substring(0, 50)}...`);
|
||||
console.log(` 内容要点: ${firstClip.content.length} 个`);
|
||||
}
|
||||
|
||||
return convertedClips;
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ API调用失败:', error.message);
|
||||
if (error.response) {
|
||||
console.error(' 状态码:', error.response.status);
|
||||
console.error(' 响应数据:', error.response.data);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
testClipsAPI().then(clips => {
|
||||
console.log(`\n🎉 测试完成,返回 ${clips.length} 个切片`);
|
||||
}).catch(error => {
|
||||
console.error('❌ 测试失败:', error);
|
||||
});
|
||||
|
||||
@@ -1,188 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
测试前端数据读取
|
||||
模拟前端的API调用和数据转换逻辑
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
|
||||
def test_frontend_clips_logic():
|
||||
"""测试前端切片数据读取逻辑"""
|
||||
print("🧪 测试前端切片数据读取逻辑...")
|
||||
|
||||
project_id = "5c48803d-0aa7-48d7-a270-2b33e4954f25"
|
||||
|
||||
try:
|
||||
# 模拟前端API调用
|
||||
response = requests.get(f"http://localhost:8000/api/v1/clips/?project_id={project_id}")
|
||||
if response.status_code != 200:
|
||||
print(f"❌ API调用失败: {response.status_code}")
|
||||
return
|
||||
|
||||
data = response.json()
|
||||
clips = data.get('items', [])
|
||||
print(f"✅ API返回 {len(clips)} 个切片")
|
||||
|
||||
# 模拟前端数据转换逻辑
|
||||
converted_clips = []
|
||||
for clip in clips:
|
||||
# 转换秒数为时间字符串格式
|
||||
def format_seconds_to_time(seconds):
|
||||
hours = int(seconds // 3600)
|
||||
minutes = int((seconds % 3600) // 60)
|
||||
secs = int(seconds % 60)
|
||||
return f"{hours:02d}:{minutes:02d}:{secs:02d}"
|
||||
|
||||
# 获取metadata中的内容
|
||||
metadata = clip.get('clip_metadata', {})
|
||||
|
||||
converted_clip = {
|
||||
'id': clip['id'],
|
||||
'title': clip['title'],
|
||||
'generated_title': clip['title'],
|
||||
'start_time': format_seconds_to_time(clip['start_time']),
|
||||
'end_time': format_seconds_to_time(clip['end_time']),
|
||||
'duration': clip.get('duration', 0),
|
||||
'final_score': clip.get('score', 0),
|
||||
'recommend_reason': metadata.get('recommend_reason', ''),
|
||||
'outline': metadata.get('outline', ''),
|
||||
'content': metadata.get('content', []),
|
||||
'chunk_index': metadata.get('chunk_index', 0)
|
||||
}
|
||||
|
||||
converted_clips.append(converted_clip)
|
||||
|
||||
print(f"✅ 转换后得到 {len(converted_clips)} 个切片")
|
||||
|
||||
if converted_clips:
|
||||
first_clip = converted_clips[0]
|
||||
print(f"📄 第一个切片示例:")
|
||||
print(f" - ID: {first_clip['id']}")
|
||||
print(f" - 标题: {first_clip['title']}")
|
||||
print(f" - 开始时间: {first_clip['start_time']}")
|
||||
print(f" - 结束时间: {first_clip['end_time']}")
|
||||
print(f" - 评分: {first_clip['final_score']}")
|
||||
print(f" - 推荐理由: {first_clip['recommend_reason']}")
|
||||
print(f" - 大纲: {first_clip['outline']}")
|
||||
print(f" - 内容要点: {len(first_clip['content'])} 个")
|
||||
|
||||
return converted_clips
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 测试失败: {e}")
|
||||
return []
|
||||
|
||||
def test_frontend_collections_logic():
|
||||
"""测试前端合集数据读取逻辑"""
|
||||
print("\n🧪 测试前端合集数据读取逻辑...")
|
||||
|
||||
project_id = "5c48803d-0aa7-48d7-a270-2b33e4954f25"
|
||||
|
||||
try:
|
||||
# 模拟前端API调用
|
||||
response = requests.get(f"http://localhost:8000/api/v1/collections/?project_id={project_id}")
|
||||
if response.status_code != 200:
|
||||
print(f"❌ API调用失败: {response.status_code}")
|
||||
return
|
||||
|
||||
data = response.json()
|
||||
collections = data.get('items', [])
|
||||
print(f"✅ API返回 {len(collections)} 个合集")
|
||||
|
||||
# 模拟前端数据转换逻辑
|
||||
converted_collections = []
|
||||
for collection in collections:
|
||||
metadata = collection.get('metadata', {})
|
||||
|
||||
converted_collection = {
|
||||
'id': collection['id'],
|
||||
'collection_title': metadata.get('collection_title', collection.get('name', '')),
|
||||
'collection_summary': metadata.get('collection_summary', collection.get('description', '')),
|
||||
'clip_ids': metadata.get('clip_ids', []),
|
||||
'collection_type': metadata.get('collection_type', 'ai_recommended'),
|
||||
'created_at': collection.get('created_at', '')
|
||||
}
|
||||
|
||||
converted_collections.append(converted_collection)
|
||||
|
||||
print(f"✅ 转换后得到 {len(converted_collections)} 个合集")
|
||||
|
||||
if converted_collections:
|
||||
first_collection = converted_collections[0]
|
||||
print(f"📄 第一个合集示例:")
|
||||
print(f" - ID: {first_collection['id']}")
|
||||
print(f" - 标题: {first_collection['collection_title']}")
|
||||
print(f" - 描述: {first_collection['collection_summary']}")
|
||||
print(f" - 切片数量: {len(first_collection['clip_ids'])}")
|
||||
print(f" - 类型: {first_collection['collection_type']}")
|
||||
|
||||
return converted_collections
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 测试失败: {e}")
|
||||
return []
|
||||
|
||||
def test_video_urls():
|
||||
"""测试视频URL生成"""
|
||||
print("\n🧪 测试视频URL生成...")
|
||||
|
||||
project_id = "5c48803d-0aa7-48d7-a270-2b33e4954f25"
|
||||
|
||||
# 测试切片视频URL
|
||||
clips_response = requests.get(f"http://localhost:8000/api/v1/clips/?project_id={project_id}")
|
||||
if clips_response.status_code == 200:
|
||||
clips = clips_response.json().get('items', [])
|
||||
if clips:
|
||||
clip_id = clips[0]['id']
|
||||
clip_video_url = f"http://localhost:8000/api/v1/projects/{project_id}/clips/{clip_id}"
|
||||
print(f"✅ 切片视频URL: {clip_video_url}")
|
||||
|
||||
# 测试URL是否可访问
|
||||
try:
|
||||
video_response = requests.head(clip_video_url)
|
||||
print(f" 状态码: {video_response.status_code}")
|
||||
except Exception as e:
|
||||
print(f" ❌ URL访问失败: {e}")
|
||||
|
||||
# 测试合集视频URL
|
||||
collections_response = requests.get(f"http://localhost:8000/api/v1/collections/?project_id={project_id}")
|
||||
if collections_response.status_code == 200:
|
||||
collections = collections_response.json().get('items', [])
|
||||
if collections:
|
||||
collection_id = collections[0]['id']
|
||||
collection_video_url = f"http://localhost:8000/api/v1/projects/{project_id}/files/output/collections/{collection_id}.mp4"
|
||||
print(f"✅ 合集视频URL: {collection_video_url}")
|
||||
|
||||
# 测试URL是否可访问
|
||||
try:
|
||||
video_response = requests.head(collection_video_url)
|
||||
print(f" 状态码: {video_response.status_code}")
|
||||
except Exception as e:
|
||||
print(f" ❌ URL访问失败: {e}")
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("🚀 开始测试前端数据读取...")
|
||||
|
||||
# 测试切片数据
|
||||
clips = test_frontend_clips_logic()
|
||||
|
||||
# 测试合集数据
|
||||
collections = test_frontend_collections_logic()
|
||||
|
||||
# 测试视频URL
|
||||
test_video_urls()
|
||||
|
||||
# 总结
|
||||
print(f"\n📊 测试总结:")
|
||||
print(f" - 切片数量: {len(clips) if clips else 0}")
|
||||
print(f" - 合集数量: {len(collections) if collections else 0}")
|
||||
|
||||
if clips and collections:
|
||||
print("✅ 前端数据读取测试通过!")
|
||||
else:
|
||||
print("❌ 前端数据读取测试失败!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,113 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
测试前端拖拽排序功能
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
|
||||
def test_frontend_reorder():
|
||||
"""测试前端拖拽排序功能"""
|
||||
|
||||
# 项目ID和合集ID
|
||||
project_id = "86f9aa12-2f35-4618-b265-74b3d9a4cf2d"
|
||||
collection_id = "5e5dafc8-f29a-4705-8e87-b2bb06f2a5de"
|
||||
|
||||
print("🎯 测试前端拖拽排序功能")
|
||||
print("=" * 50)
|
||||
|
||||
# 1. 获取初始状态
|
||||
print("1️⃣ 获取初始状态...")
|
||||
try:
|
||||
response = requests.get(f"http://localhost:8000/api/v1/collections/?project_id={project_id}")
|
||||
if response.status_code == 200:
|
||||
collections = response.json()['items']
|
||||
target_collection = next((c for c in collections if c['id'] == collection_id), None)
|
||||
if target_collection:
|
||||
initial_clip_ids = target_collection['clip_ids']
|
||||
print(f"✅ 合集: {target_collection['name']}")
|
||||
print(f"📋 初始clip_ids: {initial_clip_ids}")
|
||||
else:
|
||||
print("❌ 未找到目标合集")
|
||||
return
|
||||
else:
|
||||
print(f"❌ 获取合集失败: {response.status_code}")
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"❌ 获取合集异常: {e}")
|
||||
return
|
||||
|
||||
# 2. 模拟拖拽排序(交换前两个元素)
|
||||
print("\n2️⃣ 模拟拖拽排序(交换前两个元素)...")
|
||||
if len(initial_clip_ids) >= 2:
|
||||
new_clip_ids = [initial_clip_ids[1], initial_clip_ids[0]] + initial_clip_ids[2:]
|
||||
print(f"🔄 新顺序: {new_clip_ids}")
|
||||
|
||||
try:
|
||||
# 模拟前端API调用
|
||||
response = requests.patch(
|
||||
f"http://localhost:8000/api/v1/projects/{project_id}/collections/{collection_id}/reorder",
|
||||
json=new_clip_ids,
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
|
||||
print(f"📥 响应状态: {response.status_code}")
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
print(f"✅ 排序成功: {result.get('clip_ids', [])}")
|
||||
print(f"📝 消息: {result.get('message', '')}")
|
||||
else:
|
||||
print(f"❌ 排序失败: {response.text}")
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"❌ 排序异常: {e}")
|
||||
return
|
||||
else:
|
||||
print("❌ 片段数量不足,无法测试排序")
|
||||
return
|
||||
|
||||
# 3. 验证排序结果
|
||||
print("\n3️⃣ 验证排序结果...")
|
||||
try:
|
||||
response = requests.get(f"http://localhost:8000/api/v1/collections/?project_id={project_id}")
|
||||
if response.status_code == 200:
|
||||
collections = response.json()['items']
|
||||
target_collection = next((c for c in collections if c['id'] == collection_id), None)
|
||||
if target_collection:
|
||||
updated_clip_ids = target_collection['clip_ids']
|
||||
print(f"📋 更新后的clip_ids: {updated_clip_ids}")
|
||||
|
||||
if updated_clip_ids == new_clip_ids:
|
||||
print("✅ 排序验证成功!")
|
||||
else:
|
||||
print("❌ 排序验证失败!")
|
||||
else:
|
||||
print("❌ 未找到目标合集")
|
||||
else:
|
||||
print(f"❌ 验证失败: {response.status_code}")
|
||||
except Exception as e:
|
||||
print(f"❌ 验证异常: {e}")
|
||||
|
||||
# 4. 恢复原始顺序
|
||||
print("\n4️⃣ 恢复原始顺序...")
|
||||
try:
|
||||
response = requests.patch(
|
||||
f"http://localhost:8000/api/v1/projects/{project_id}/collections/{collection_id}/reorder",
|
||||
json=initial_clip_ids,
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
|
||||
print(f"📥 响应状态: {response.status_code}")
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
print(f"✅ 恢复成功: {result.get('clip_ids', [])}")
|
||||
else:
|
||||
print(f"❌ 恢复失败: {response.text}")
|
||||
except Exception as e:
|
||||
print(f"❌ 恢复异常: {e}")
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("🎉 前端拖拽排序功能测试完成!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_frontend_reorder()
|
||||
@@ -1,119 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
测试流水线修复后的功能
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
project_root = Path(__file__).parent.parent
|
||||
backend_path = project_root / "backend"
|
||||
sys.path.insert(0, str(backend_path))
|
||||
|
||||
def test_pipeline_imports():
|
||||
"""测试流水线模块导入"""
|
||||
print("测试流水线模块导入...")
|
||||
|
||||
try:
|
||||
from pipeline.step1_outline import OutlineExtractor
|
||||
from pipeline.step2_timeline import TimelineExtractor
|
||||
from pipeline.step3_scoring import ClipScorer
|
||||
from pipeline.step4_title import TitleGenerator
|
||||
from pipeline.step5_clustering import ClusteringEngine
|
||||
from pipeline.step6_video import VideoGenerator
|
||||
print("✓ 所有流水线步骤导入成功")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"✗ 流水线模块导入失败: {e}")
|
||||
return False
|
||||
|
||||
def test_pipeline_adapter():
|
||||
"""测试Pipeline适配器"""
|
||||
print("\n测试Pipeline适配器...")
|
||||
|
||||
try:
|
||||
from services.pipeline_adapter import PipelineAdapter
|
||||
from core.database import SessionLocal
|
||||
|
||||
# 创建数据库会话
|
||||
db = SessionLocal()
|
||||
|
||||
# 创建适配器
|
||||
adapter = PipelineAdapter(db, task_id="test-task", project_id="test-project")
|
||||
print("✓ Pipeline适配器创建成功")
|
||||
|
||||
db.close()
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"✗ Pipeline适配器测试失败: {e}")
|
||||
return False
|
||||
|
||||
def test_websocket_service():
|
||||
"""测试WebSocket通知服务"""
|
||||
print("\n测试WebSocket通知服务...")
|
||||
|
||||
try:
|
||||
from services.websocket_notification_service import WebSocketNotificationService
|
||||
|
||||
# 测试方法签名
|
||||
service = WebSocketNotificationService()
|
||||
print("✓ WebSocket通知服务创建成功")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"✗ WebSocket通知服务测试失败: {e}")
|
||||
return False
|
||||
|
||||
def test_progress_manager():
|
||||
"""测试进度管理器"""
|
||||
print("\n测试进度管理器...")
|
||||
|
||||
try:
|
||||
from core.progress_manager import ProgressManager
|
||||
from core.database import SessionLocal
|
||||
|
||||
# 创建数据库会话
|
||||
db = SessionLocal()
|
||||
|
||||
# 创建进度管理器
|
||||
progress_manager = ProgressManager(db)
|
||||
print("✓ 进度管理器创建成功")
|
||||
|
||||
db.close()
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"✗ 进度管理器测试失败: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
"""主测试函数"""
|
||||
print("开始测试流水线修复...")
|
||||
print("=" * 50)
|
||||
|
||||
tests = [
|
||||
test_pipeline_imports,
|
||||
test_pipeline_adapter,
|
||||
test_websocket_service,
|
||||
test_progress_manager
|
||||
]
|
||||
|
||||
passed = 0
|
||||
total = len(tests)
|
||||
|
||||
for test in tests:
|
||||
if test():
|
||||
passed += 1
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print(f"测试结果: {passed}/{total} 通过")
|
||||
|
||||
if passed == total:
|
||||
print("✓ 所有测试通过!流水线修复成功")
|
||||
return 0
|
||||
else:
|
||||
print("✗ 部分测试失败,需要进一步修复")
|
||||
return 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -1,211 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
测试项目创建修复
|
||||
验证B站和YouTube项目创建时的字段映射问题是否已修复
|
||||
"""
|
||||
|
||||
import sys
|
||||
import asyncio
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
# 设置环境变量
|
||||
import os
|
||||
os.environ['PYTHONPATH'] = str(project_root)
|
||||
|
||||
# 添加backend目录到路径
|
||||
backend_dir = project_root / "backend"
|
||||
sys.path.insert(0, str(backend_dir))
|
||||
|
||||
from backend.core.database import SessionLocal
|
||||
from backend.services.project_service import ProjectService
|
||||
from backend.schemas.project import ProjectCreate, ProjectType, ProjectStatus
|
||||
|
||||
# 配置日志
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def test_project_creation():
|
||||
"""测试项目创建修复"""
|
||||
print("🔍 测试项目创建修复...")
|
||||
|
||||
try:
|
||||
# 创建数据库会话
|
||||
db = SessionLocal()
|
||||
project_service = ProjectService(db)
|
||||
|
||||
try:
|
||||
# 测试1: 创建B站项目
|
||||
print("1. 测试B站项目创建...")
|
||||
bilibili_project_data = ProjectCreate(
|
||||
name="测试B站项目",
|
||||
description="测试B站项目创建",
|
||||
project_type=ProjectType.KNOWLEDGE,
|
||||
source_url="https://www.bilibili.com/video/BV1xx411c7mu",
|
||||
source_file="/path/to/video.mp4",
|
||||
settings={
|
||||
"bilibili_info": {
|
||||
"title": "测试视频",
|
||||
"uploader": "测试UP主",
|
||||
"duration": 300,
|
||||
"view_count": 1000
|
||||
},
|
||||
"subtitle_path": "/path/to/subtitle.srt"
|
||||
}
|
||||
)
|
||||
|
||||
bilibili_project = project_service.create_project(bilibili_project_data)
|
||||
print(f" ✅ B站项目创建成功: {bilibili_project.id}")
|
||||
|
||||
# 验证字段映射
|
||||
print(f" 📋 项目字段验证:")
|
||||
print(f" - video_path: {bilibili_project.video_path}")
|
||||
print(f" - processing_config: {bilibili_project.processing_config}")
|
||||
print(f" - project_metadata: {bilibili_project.project_metadata}")
|
||||
|
||||
# 测试2: 创建YouTube项目
|
||||
print("2. 测试YouTube项目创建...")
|
||||
youtube_project_data = ProjectCreate(
|
||||
name="测试YouTube项目",
|
||||
description="测试YouTube项目创建",
|
||||
project_type=ProjectType.ENTERTAINMENT,
|
||||
source_url="https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
||||
source_file="/path/to/youtube_video.mp4",
|
||||
settings={
|
||||
"youtube_info": {
|
||||
"title": "测试YouTube视频",
|
||||
"uploader": "YouTube",
|
||||
"duration": 240,
|
||||
"view_count": 5000
|
||||
},
|
||||
"subtitle_path": "/path/to/youtube_subtitle.srt"
|
||||
}
|
||||
)
|
||||
|
||||
youtube_project = project_service.create_project(youtube_project_data)
|
||||
print(f" ✅ YouTube项目创建成功: {youtube_project.id}")
|
||||
|
||||
# 验证字段映射
|
||||
print(f" 📋 项目字段验证:")
|
||||
print(f" - video_path: {youtube_project.video_path}")
|
||||
print(f" - processing_config: {youtube_project.processing_config}")
|
||||
print(f" - project_metadata: {youtube_project.project_metadata}")
|
||||
|
||||
# 测试3: 测试字段更新
|
||||
print("3. 测试字段更新...")
|
||||
|
||||
# 测试B站项目的字段更新
|
||||
if not bilibili_project.processing_config:
|
||||
bilibili_project.processing_config = {}
|
||||
bilibili_project.processing_config["subtitle_path"] = "/new/path/to/subtitle.srt"
|
||||
bilibili_project.video_path = "/new/path/to/video.mp4"
|
||||
db.commit()
|
||||
print(f" ✅ B站项目字段更新成功")
|
||||
|
||||
# 测试YouTube项目的字段更新
|
||||
if not youtube_project.processing_config:
|
||||
youtube_project.processing_config = {}
|
||||
youtube_project.processing_config["subtitle_path"] = "/new/path/to/youtube_subtitle.srt"
|
||||
youtube_project.video_path = "/new/path/to/youtube_video.mp4"
|
||||
db.commit()
|
||||
print(f" ✅ YouTube项目字段更新成功")
|
||||
|
||||
# 清理测试数据
|
||||
print("4. 清理测试数据...")
|
||||
db.delete(bilibili_project)
|
||||
db.delete(youtube_project)
|
||||
db.commit()
|
||||
print(f" ✅ 测试数据清理完成")
|
||||
|
||||
print("✅ 项目创建修复测试完成")
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 项目创建修复测试失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
def test_field_access():
|
||||
"""测试字段访问"""
|
||||
print("\n🔍 测试字段访问...")
|
||||
|
||||
try:
|
||||
# 创建数据库会话
|
||||
db = SessionLocal()
|
||||
project_service = ProjectService(db)
|
||||
|
||||
try:
|
||||
# 创建测试项目
|
||||
test_project_data = ProjectCreate(
|
||||
name="字段访问测试",
|
||||
description="测试字段访问",
|
||||
project_type=ProjectType.DEFAULT,
|
||||
settings={"test_key": "test_value"}
|
||||
)
|
||||
|
||||
project = project_service.create_project(test_project_data)
|
||||
|
||||
# 测试各种字段访问
|
||||
print("1. 测试基本字段访问...")
|
||||
print(f" - project.id: {project.id}")
|
||||
print(f" - project.name: {project.name}")
|
||||
print(f" - project.status: {project.status}")
|
||||
print(f" - project.project_type: {project.project_type}")
|
||||
|
||||
print("2. 测试配置字段访问...")
|
||||
print(f" - project.processing_config: {project.processing_config}")
|
||||
print(f" - project.project_metadata: {project.project_metadata}")
|
||||
|
||||
print("3. 测试计算属性...")
|
||||
print(f" - project.has_video_file: {project.has_video_file}")
|
||||
print(f" - project.has_subtitle_file: {project.has_subtitle_file}")
|
||||
print(f" - project.is_processing: {project.is_processing}")
|
||||
print(f" - project.is_completed: {project.is_completed}")
|
||||
|
||||
# 测试字段更新
|
||||
print("4. 测试字段更新...")
|
||||
project.processing_config["new_key"] = "new_value"
|
||||
project.video_path = "/test/video.mp4"
|
||||
project.subtitle_path = "/test/subtitle.srt"
|
||||
db.commit()
|
||||
|
||||
print(f" - 更新后 processing_config: {project.processing_config}")
|
||||
print(f" - 更新后 video_path: {project.video_path}")
|
||||
print(f" - 更新后 subtitle_path: {project.subtitle_path}")
|
||||
print(f" - 更新后 has_video_file: {project.has_video_file}")
|
||||
print(f" - 更新后 has_subtitle_file: {project.has_subtitle_file}")
|
||||
|
||||
# 清理测试数据
|
||||
db.delete(project)
|
||||
db.commit()
|
||||
|
||||
print("✅ 字段访问测试完成")
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 字段访问测试失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("🚀 开始测试项目创建修复...")
|
||||
|
||||
# 测试项目创建
|
||||
test_project_creation()
|
||||
|
||||
# 测试字段访问
|
||||
test_field_access()
|
||||
|
||||
print("\n🎉 所有测试完成!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,155 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
测试重试功能修复
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
project_root = Path(__file__).parent.parent
|
||||
backend_path = project_root / "backend"
|
||||
sys.path.insert(0, str(backend_path))
|
||||
|
||||
def test_retry_api():
|
||||
"""测试重试API"""
|
||||
print("测试重试API...")
|
||||
|
||||
try:
|
||||
from api.v1.projects import retry_processing
|
||||
print("✓ 重试API导入成功")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"✗ 重试API导入失败: {e}")
|
||||
return False
|
||||
|
||||
def test_project_status_check():
|
||||
"""测试项目状态检查逻辑"""
|
||||
print("\n测试项目状态检查逻辑...")
|
||||
|
||||
try:
|
||||
# 模拟项目状态检查逻辑
|
||||
allowed_statuses = ["failed", "completed", "processing"]
|
||||
|
||||
test_cases = [
|
||||
("failed", True),
|
||||
("completed", True),
|
||||
("processing", True),
|
||||
("pending", False),
|
||||
("cancelled", False)
|
||||
]
|
||||
|
||||
for status, should_allow in test_cases:
|
||||
is_allowed = status in allowed_statuses
|
||||
if is_allowed == should_allow:
|
||||
print(f"✓ 状态 '{status}' 检查正确: {'允许' if is_allowed else '拒绝'}")
|
||||
else:
|
||||
print(f"✗ 状态 '{status}' 检查错误: {'允许' if is_allowed else '拒绝'}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ 项目状态检查测试失败: {e}")
|
||||
return False
|
||||
|
||||
def test_file_path_logic():
|
||||
"""测试文件路径逻辑"""
|
||||
print("\n测试文件路径逻辑...")
|
||||
|
||||
try:
|
||||
from pathlib import Path
|
||||
|
||||
# 测试项目目录
|
||||
project_id = "86f9aa12-2f35-4618-b265-74b3d9a4cf2d"
|
||||
raw_dir = Path(f"data/projects/{project_id}/raw")
|
||||
|
||||
if raw_dir.exists():
|
||||
print(f"✓ 项目目录存在: {raw_dir}")
|
||||
|
||||
# 检查视频文件
|
||||
video_files = list(raw_dir.glob("*.mp4"))
|
||||
if video_files:
|
||||
print(f"✓ 找到视频文件: {video_files[0]}")
|
||||
else:
|
||||
print("✗ 未找到视频文件")
|
||||
return False
|
||||
|
||||
# 检查SRT文件
|
||||
srt_files = list(raw_dir.glob("*.srt"))
|
||||
if srt_files:
|
||||
print(f"✓ 找到SRT文件: {srt_files[0]}")
|
||||
else:
|
||||
print("✗ 未找到SRT文件")
|
||||
return False
|
||||
else:
|
||||
print(f"✗ 项目目录不存在: {raw_dir}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ 文件路径逻辑测试失败: {e}")
|
||||
return False
|
||||
|
||||
def test_websocket_error_fix():
|
||||
"""测试WebSocket错误修复"""
|
||||
print("\n测试WebSocket错误修复...")
|
||||
|
||||
try:
|
||||
from services.websocket_notification_service import WebSocketNotificationService
|
||||
|
||||
# 测试send_processing_error方法签名
|
||||
service = WebSocketNotificationService()
|
||||
import inspect
|
||||
sig = inspect.signature(service.send_processing_error)
|
||||
params = list(sig.parameters.keys())
|
||||
|
||||
if 'task_id' in params and 'project_id' in params and 'error' in params:
|
||||
print("✓ send_processing_error方法参数正确")
|
||||
return True
|
||||
else:
|
||||
print(f"✗ send_processing_error方法参数不正确: {params}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ WebSocket错误修复测试失败: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
"""主测试函数"""
|
||||
print("开始测试重试功能修复...")
|
||||
print("=" * 50)
|
||||
|
||||
tests = [
|
||||
test_retry_api,
|
||||
test_project_status_check,
|
||||
test_file_path_logic,
|
||||
test_websocket_error_fix
|
||||
]
|
||||
|
||||
passed = 0
|
||||
total = len(tests)
|
||||
|
||||
for test in tests:
|
||||
if test():
|
||||
passed += 1
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print(f"测试结果: {passed}/{total} 通过")
|
||||
|
||||
if passed == total:
|
||||
print("✓ 所有测试通过!重试功能修复成功")
|
||||
print("\n修复总结:")
|
||||
print("1. ✅ 允许processing状态的项目重试")
|
||||
print("2. ✅ 重试前取消当前运行的任务")
|
||||
print("3. ✅ 修复WebSocket错误通知参数")
|
||||
print("4. ✅ 文件路径检查逻辑正确")
|
||||
return 0
|
||||
else:
|
||||
print("✗ 部分测试失败,需要进一步修复")
|
||||
return 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -1,252 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
语音识别模块测试脚本
|
||||
用于验证重新设计的语音识别功能
|
||||
"""
|
||||
import sys
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
from backend.utils.speech_recognizer import (
|
||||
SpeechRecognizer,
|
||||
SpeechRecognitionConfig,
|
||||
SpeechRecognitionMethod,
|
||||
LanguageCode,
|
||||
get_available_speech_recognition_methods,
|
||||
get_supported_languages,
|
||||
get_whisper_models,
|
||||
SpeechRecognitionError
|
||||
)
|
||||
|
||||
# 配置日志
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def test_speech_recognition_status():
|
||||
"""测试语音识别状态查询"""
|
||||
print("🔍 测试语音识别状态查询...")
|
||||
|
||||
try:
|
||||
# 获取可用方法
|
||||
available_methods = get_available_speech_recognition_methods()
|
||||
print(f"✅ 可用方法: {available_methods}")
|
||||
|
||||
# 获取支持的语言
|
||||
supported_languages = get_supported_languages()
|
||||
print(f"✅ 支持的语言: {supported_languages}")
|
||||
|
||||
# 获取Whisper模型
|
||||
whisper_models = get_whisper_models()
|
||||
print(f"✅ Whisper模型: {whisper_models}")
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ 状态查询失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_speech_recognizer_initialization():
|
||||
"""测试语音识别器初始化"""
|
||||
print("\n🔧 测试语音识别器初始化...")
|
||||
|
||||
try:
|
||||
# 创建默认配置的识别器
|
||||
recognizer = SpeechRecognizer()
|
||||
print("✅ 默认配置识别器创建成功")
|
||||
|
||||
# 创建自定义配置的识别器
|
||||
config = SpeechRecognitionConfig(
|
||||
method=SpeechRecognitionMethod.WHISPER_LOCAL,
|
||||
language=LanguageCode.CHINESE_SIMPLIFIED,
|
||||
model="base",
|
||||
timeout=300
|
||||
)
|
||||
recognizer = SpeechRecognizer(config)
|
||||
print("✅ 自定义配置识别器创建成功")
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ 识别器初始化失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_configuration_validation():
|
||||
"""测试配置验证"""
|
||||
print("\n⚙️ 测试配置验证...")
|
||||
|
||||
try:
|
||||
# 测试有效配置
|
||||
config = SpeechRecognitionConfig(
|
||||
method=SpeechRecognitionMethod.WHISPER_LOCAL,
|
||||
language=LanguageCode.CHINESE_SIMPLIFIED,
|
||||
model="base"
|
||||
)
|
||||
print("✅ 有效配置验证通过")
|
||||
|
||||
# 测试无效方法(应该抛出异常)
|
||||
try:
|
||||
invalid_config = SpeechRecognitionConfig(
|
||||
method="invalid_method",
|
||||
language=LanguageCode.CHINESE_SIMPLIFIED
|
||||
)
|
||||
print("❌ 无效方法配置应该抛出异常")
|
||||
return False
|
||||
except ValueError:
|
||||
print("✅ 无效方法配置正确抛出异常")
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ 配置验证失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_error_handling():
|
||||
"""测试错误处理"""
|
||||
print("\n🚨 测试错误处理...")
|
||||
|
||||
try:
|
||||
recognizer = SpeechRecognizer()
|
||||
|
||||
# 测试不存在的视频文件
|
||||
non_existent_video = Path("/path/to/non/existent/video.mp4")
|
||||
|
||||
try:
|
||||
result = recognizer.generate_subtitle(non_existent_video)
|
||||
print("❌ 不存在的文件应该抛出异常")
|
||||
return False
|
||||
except SpeechRecognitionError as e:
|
||||
print(f"✅ 不存在的文件正确抛出异常: {e}")
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ 错误处理测试失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_language_support():
|
||||
"""测试语言支持"""
|
||||
print("\n🌍 测试语言支持...")
|
||||
|
||||
try:
|
||||
# 测试中文配置
|
||||
config_zh = SpeechRecognitionConfig(
|
||||
language=LanguageCode.CHINESE_SIMPLIFIED
|
||||
)
|
||||
print("✅ 中文配置创建成功")
|
||||
|
||||
# 测试英文配置
|
||||
config_en = SpeechRecognitionConfig(
|
||||
language=LanguageCode.ENGLISH
|
||||
)
|
||||
print("✅ 英文配置创建成功")
|
||||
|
||||
# 测试自动检测
|
||||
config_auto = SpeechRecognitionConfig(
|
||||
language=LanguageCode.AUTO
|
||||
)
|
||||
print("✅ 自动检测配置创建成功")
|
||||
|
||||
# 测试日文配置
|
||||
config_ja = SpeechRecognitionConfig(
|
||||
language=LanguageCode.JAPANESE
|
||||
)
|
||||
print("✅ 日文配置创建成功")
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ 语言支持测试失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_method_availability():
|
||||
"""测试方法可用性检查"""
|
||||
print("\n🔍 测试方法可用性检查...")
|
||||
|
||||
try:
|
||||
recognizer = SpeechRecognizer()
|
||||
available_methods = recognizer.get_available_methods()
|
||||
|
||||
print(f"✅ 可用方法检查成功: {available_methods}")
|
||||
|
||||
# 检查是否有至少一个可用方法
|
||||
if any(available_methods.values()):
|
||||
print("✅ 至少有一个语音识别方法可用")
|
||||
else:
|
||||
print("⚠️ 没有可用的语音识别方法")
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ 方法可用性检查失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_whisper_models():
|
||||
"""测试Whisper模型配置"""
|
||||
print("\n🤖 测试Whisper模型配置...")
|
||||
|
||||
try:
|
||||
models = ["tiny", "base", "small", "medium", "large"]
|
||||
|
||||
for model in models:
|
||||
config = SpeechRecognitionConfig(
|
||||
method=SpeechRecognitionMethod.WHISPER_LOCAL,
|
||||
model=model
|
||||
)
|
||||
print(f"✅ {model} 模型配置创建成功")
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ Whisper模型配置测试失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
"""主测试函数"""
|
||||
print("🎤 语音识别模块测试开始")
|
||||
print("=" * 50)
|
||||
|
||||
tests = [
|
||||
("状态查询", test_speech_recognition_status),
|
||||
("识别器初始化", test_speech_recognizer_initialization),
|
||||
("配置验证", test_configuration_validation),
|
||||
("错误处理", test_error_handling),
|
||||
("语言支持", test_language_support),
|
||||
("方法可用性", test_method_availability),
|
||||
("Whisper模型", test_whisper_models),
|
||||
]
|
||||
|
||||
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" + "=" * 50)
|
||||
print(f"📊 测试结果: {passed}/{total} 通过")
|
||||
|
||||
if passed == total:
|
||||
print("🎉 所有测试通过!语音识别模块工作正常")
|
||||
return 0
|
||||
else:
|
||||
print("⚠️ 部分测试失败,请检查配置和依赖")
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
测试步骤3修复
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
project_root = Path(__file__).parent.parent
|
||||
backend_path = project_root / "backend"
|
||||
sys.path.insert(0, str(backend_path))
|
||||
|
||||
def test_step3_import():
|
||||
"""测试步骤3导入"""
|
||||
print("测试步骤3导入...")
|
||||
|
||||
try:
|
||||
from pipeline.step3_scoring import ClipScorer
|
||||
print("✓ 步骤3导入成功")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"✗ 步骤3导入失败: {e}")
|
||||
return False
|
||||
|
||||
def test_step3_config():
|
||||
"""测试步骤3配置"""
|
||||
print("\n测试步骤3配置...")
|
||||
|
||||
try:
|
||||
from pipeline.config import PROMPT_FILES
|
||||
|
||||
# 检查recommendation键是否存在
|
||||
if 'recommendation' in PROMPT_FILES:
|
||||
print("✓ recommendation键存在")
|
||||
print(f" 路径: {PROMPT_FILES['recommendation']}")
|
||||
|
||||
# 检查文件是否存在
|
||||
if PROMPT_FILES['recommendation'].exists():
|
||||
print("✓ 推荐理由文件存在")
|
||||
return True
|
||||
else:
|
||||
print("✗ 推荐理由文件不存在")
|
||||
return False
|
||||
else:
|
||||
print("✗ recommendation键不存在")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ 步骤3配置测试失败: {e}")
|
||||
return False
|
||||
|
||||
def test_step3_initialization():
|
||||
"""测试步骤3初始化"""
|
||||
print("\n测试步骤3初始化...")
|
||||
|
||||
try:
|
||||
from pipeline.step3_scoring import ClipScorer
|
||||
|
||||
# 创建实例
|
||||
scorer = ClipScorer()
|
||||
print("✓ 步骤3实例创建成功")
|
||||
|
||||
# 检查提示词是否加载
|
||||
if hasattr(scorer, 'recommendation_prompt') and scorer.recommendation_prompt:
|
||||
print("✓ 推荐理由提示词加载成功")
|
||||
print(f" 提示词长度: {len(scorer.recommendation_prompt)} 字符")
|
||||
return True
|
||||
else:
|
||||
print("✗ 推荐理由提示词加载失败")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ 步骤3初始化失败: {e}")
|
||||
return False
|
||||
|
||||
def test_websocket_fix():
|
||||
"""测试WebSocket修复"""
|
||||
print("\n测试WebSocket修复...")
|
||||
|
||||
try:
|
||||
from services.websocket_notification_service import WebSocketNotificationService
|
||||
|
||||
# 测试方法签名
|
||||
service = WebSocketNotificationService()
|
||||
|
||||
# 检查send_task_update方法
|
||||
import inspect
|
||||
sig = inspect.signature(service.send_task_update)
|
||||
params = list(sig.parameters.keys())
|
||||
|
||||
if 'project_id' not in params:
|
||||
print("✓ send_task_update方法不包含project_id参数")
|
||||
return True
|
||||
else:
|
||||
print("✗ send_task_update方法仍然包含project_id参数")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ WebSocket修复测试失败: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
"""主测试函数"""
|
||||
print("开始测试步骤3修复...")
|
||||
print("=" * 50)
|
||||
|
||||
tests = [
|
||||
test_step3_import,
|
||||
test_step3_config,
|
||||
test_step3_initialization,
|
||||
test_websocket_fix
|
||||
]
|
||||
|
||||
passed = 0
|
||||
total = len(tests)
|
||||
|
||||
for test in tests:
|
||||
if test():
|
||||
passed += 1
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print(f"测试结果: {passed}/{total} 通过")
|
||||
|
||||
if passed == total:
|
||||
print("✓ 所有测试通过!步骤3修复成功")
|
||||
return 0
|
||||
else:
|
||||
print("✗ 部分测试失败,需要进一步修复")
|
||||
return 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -1,245 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
测试字幕下载修复
|
||||
验证B站和YouTube字幕下载的修复是否有效
|
||||
"""
|
||||
|
||||
import sys
|
||||
import asyncio
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
# 设置环境变量
|
||||
import os
|
||||
os.environ['PYTHONPATH'] = str(project_root)
|
||||
|
||||
# 添加backend目录到路径
|
||||
backend_dir = project_root / "backend"
|
||||
sys.path.insert(0, str(backend_dir))
|
||||
|
||||
from backend.utils.bilibili_downloader import BilibiliDownloader, get_bilibili_video_info
|
||||
from backend.utils.speech_recognizer import get_available_speech_recognition_methods
|
||||
import yt_dlp
|
||||
|
||||
# 配置日志
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
async def test_bilibili_subtitle_download():
|
||||
"""测试B站字幕下载修复"""
|
||||
print("🔍 测试B站字幕下载修复...")
|
||||
|
||||
# 测试URL(使用一个已知有字幕的B站视频)
|
||||
test_url = "https://www.bilibili.com/video/BV1xx411c7mu"
|
||||
|
||||
try:
|
||||
# 1. 测试视频信息获取
|
||||
print("1. 测试视频信息获取...")
|
||||
video_info = await get_bilibili_video_info(test_url, "chrome")
|
||||
print(f"✅ 视频信息获取成功: {video_info.title}")
|
||||
|
||||
# 2. 测试字幕下载
|
||||
print("2. 测试字幕下载...")
|
||||
downloader = BilibiliDownloader(download_dir=Path("test_downloads"), browser="chrome")
|
||||
|
||||
# 测试不同的字幕下载策略
|
||||
strategies = [
|
||||
("默认策略", {"subtitleslangs": ["ai-zh"], "writeautomaticsub": False}),
|
||||
("多语言策略", {"subtitleslangs": ["ai-zh", "zh-Hans", "zh", "en"], "writeautomaticsub": True}),
|
||||
("无cookies策略", {"subtitleslangs": ["zh-Hans", "zh"], "writeautomaticsub": True, "cookies": None}),
|
||||
]
|
||||
|
||||
for name, opts in strategies:
|
||||
print(f" 测试策略: {name}")
|
||||
try:
|
||||
ydl_opts = {
|
||||
'skip_download': True, # 只下载字幕,不下载视频
|
||||
'writesubtitles': True,
|
||||
'outtmpl': f'test_downloads/test_{name}.%(ext)s',
|
||||
'noplaylist': True,
|
||||
'quiet': True,
|
||||
**opts
|
||||
}
|
||||
|
||||
if "cookies" not in opts or opts["cookies"] is not None:
|
||||
ydl_opts['cookiesfrombrowser'] = ('chrome',)
|
||||
|
||||
def download_sync(url, ydl_opts):
|
||||
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||
return ydl.download([url])
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
await loop.run_in_executor(None, download_sync, test_url, ydl_opts)
|
||||
|
||||
# 检查是否下载了字幕文件
|
||||
subtitle_files = list(Path("test_downloads").glob("*.srt"))
|
||||
success = len(subtitle_files) > 0
|
||||
print(f" ✅ 策略 {name}: {'成功' if success else '失败'}")
|
||||
|
||||
if success:
|
||||
print(f" 📄 字幕文件: {[f.name for f in subtitle_files]}")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ 策略 {name} 失败: {e}")
|
||||
|
||||
# 3. 测试语音识别设置
|
||||
print("3. 测试语音识别设置...")
|
||||
available_methods = get_available_speech_recognition_methods()
|
||||
print(f" 可用的语音识别方法: {available_methods}")
|
||||
|
||||
if any(available_methods.values()):
|
||||
print(" ✅ 语音识别备用方案可用")
|
||||
else:
|
||||
print(" ⚠️ 语音识别备用方案不可用,建议安装Whisper")
|
||||
|
||||
print("✅ B站字幕下载测试完成")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ B站字幕下载测试失败: {e}")
|
||||
|
||||
async def test_youtube_subtitle_download():
|
||||
"""测试YouTube字幕下载修复"""
|
||||
print("\n🔍 测试YouTube字幕下载修复...")
|
||||
|
||||
# 测试URL(使用一个已知有字幕的YouTube视频)
|
||||
test_url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
|
||||
|
||||
try:
|
||||
# 1. 测试视频信息获取
|
||||
print("1. 测试视频信息获取...")
|
||||
|
||||
ydl_opts = {
|
||||
'quiet': True,
|
||||
'no_warnings': True,
|
||||
}
|
||||
|
||||
def extract_info_sync(url, ydl_opts):
|
||||
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||
return ydl.extract_info(url, download=False)
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
info_dict = await loop.run_in_executor(None, extract_info_sync, test_url, ydl_opts)
|
||||
|
||||
print(f"✅ 视频信息获取成功: {info_dict.get('title', 'Unknown')}")
|
||||
|
||||
# 2. 测试字幕下载
|
||||
print("2. 测试字幕下载...")
|
||||
|
||||
# 测试不同的字幕下载策略
|
||||
strategies = [
|
||||
("默认策略", {"subtitleslangs": ["en", "zh-Hans"], "writeautomaticsub": True}),
|
||||
("多语言策略", {"subtitleslangs": ["en", "zh-Hans", "zh", "ja", "ko"], "writeautomaticsub": True}),
|
||||
("多格式策略", {"subtitleslangs": ["en", "zh-Hans"], "subtitlesformat": "vtt", "writeautomaticsub": True}),
|
||||
]
|
||||
|
||||
for name, opts in strategies:
|
||||
print(f" 测试策略: {name}")
|
||||
try:
|
||||
ydl_opts = {
|
||||
'skip_download': True, # 只下载字幕,不下载视频
|
||||
'writesubtitles': True,
|
||||
'outtmpl': f'test_downloads/youtube_test_{name}.%(ext)s',
|
||||
'noplaylist': True,
|
||||
'quiet': True,
|
||||
**opts
|
||||
}
|
||||
|
||||
def download_sync(url, ydl_opts):
|
||||
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||
return ydl.download([url])
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
await loop.run_in_executor(None, download_sync, test_url, ydl_opts)
|
||||
|
||||
# 检查是否下载了字幕文件
|
||||
subtitle_files = list(Path("test_downloads").glob("*.srt")) + list(Path("test_downloads").glob("*.vtt"))
|
||||
success = len(subtitle_files) > 0
|
||||
print(f" ✅ 策略 {name}: {'成功' if success else '失败'}")
|
||||
|
||||
if success:
|
||||
print(f" 📄 字幕文件: {[f.name for f in subtitle_files]}")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ 策略 {name} 失败: {e}")
|
||||
|
||||
print("✅ YouTube字幕下载测试完成")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ YouTube字幕下载测试失败: {e}")
|
||||
|
||||
async def test_srt_path_handling():
|
||||
"""测试SRT路径处理修复"""
|
||||
print("\n🔍 测试SRT路径处理修复...")
|
||||
|
||||
try:
|
||||
from backend.services.pipeline_adapter import PipelineAdapter
|
||||
from backend.core.database import SessionLocal
|
||||
|
||||
# 创建测试目录
|
||||
test_dir = Path("test_srt_handling")
|
||||
test_dir.mkdir(exist_ok=True)
|
||||
|
||||
# 测试不同的SRT路径情况
|
||||
test_cases = [
|
||||
("有效SRT路径", str(test_dir / "valid.srt")),
|
||||
("空字符串", ""),
|
||||
("None值", None),
|
||||
("无效路径", "/invalid/path/file.srt"),
|
||||
]
|
||||
|
||||
for case_name, srt_path in test_cases:
|
||||
print(f" 测试情况: {case_name}")
|
||||
try:
|
||||
# 模拟Pipeline适配器的SRT路径验证
|
||||
srt_file_path = Path(srt_path) if srt_path and srt_path.strip() else None
|
||||
|
||||
if not srt_file_path or not srt_file_path.exists():
|
||||
print(f" ⚠️ SRT文件不存在或路径无效: {srt_path}")
|
||||
print(f" ✅ 正确处理了无效路径")
|
||||
else:
|
||||
print(f" ✅ SRT文件路径有效: {srt_file_path}")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ 处理失败: {e}")
|
||||
|
||||
# 清理测试目录
|
||||
import shutil
|
||||
shutil.rmtree(test_dir, ignore_errors=True)
|
||||
|
||||
print("✅ SRT路径处理测试完成")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ SRT路径处理测试失败: {e}")
|
||||
|
||||
async def main():
|
||||
"""主函数"""
|
||||
print("🚀 开始测试字幕下载修复...")
|
||||
|
||||
# 创建测试下载目录
|
||||
test_downloads_dir = Path("test_downloads")
|
||||
test_downloads_dir.mkdir(exist_ok=True)
|
||||
|
||||
try:
|
||||
# 测试B站字幕下载
|
||||
await test_bilibili_subtitle_download()
|
||||
|
||||
# 测试YouTube字幕下载
|
||||
await test_youtube_subtitle_download()
|
||||
|
||||
# 测试SRT路径处理
|
||||
await test_srt_path_handling()
|
||||
|
||||
print("\n🎉 所有测试完成!")
|
||||
|
||||
finally:
|
||||
# 清理测试文件
|
||||
import shutil
|
||||
shutil.rmtree(test_downloads_dir, ignore_errors=True)
|
||||
print("🧹 测试文件已清理")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -1,64 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
测试同步特定项目
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
# 添加backend目录到路径
|
||||
backend_dir = project_root / "backend"
|
||||
sys.path.insert(0, str(backend_dir))
|
||||
|
||||
from core.database import SessionLocal
|
||||
from services.data_sync_service import DataSyncService
|
||||
from core.config import get_data_directory
|
||||
|
||||
def test_sync_project(project_id: str):
|
||||
"""测试同步特定项目"""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
data_sync_service = DataSyncService(db)
|
||||
data_dir = get_data_directory()
|
||||
project_dir = data_dir / "projects" / project_id
|
||||
|
||||
print(f"项目目录: {project_dir}")
|
||||
print(f"项目目录存在: {project_dir.exists()}")
|
||||
|
||||
if project_dir.exists():
|
||||
print(f"项目目录内容:")
|
||||
for item in project_dir.iterdir():
|
||||
print(f" - {item.name}")
|
||||
|
||||
# 检查切片文件
|
||||
step4_file = project_dir / "step4_titles.json"
|
||||
print(f"\n检查切片文件: {step4_file}")
|
||||
print(f"文件存在: {step4_file.exists()}")
|
||||
|
||||
if step4_file.exists():
|
||||
try:
|
||||
with open(step4_file, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
print(f"文件内容长度: {len(data)}")
|
||||
print(f"第一个切片: {data[0] if data else 'None'}")
|
||||
except Exception as e:
|
||||
print(f"读取文件失败: {e}")
|
||||
|
||||
# 手动同步
|
||||
print(f"\n开始手动同步...")
|
||||
result = data_sync_service.sync_project_from_filesystem(project_id, project_dir)
|
||||
print(f"同步结果: {result}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"错误: {e}")
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
project_id = "1fdb0bf1-7f3c-44f7-a69d-90c5a1d26fbe"
|
||||
test_sync_project(project_id)
|
||||
@@ -1,164 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>文本截断测试</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: #1a1a1a;
|
||||
color: #ffffff;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.test-container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.test-card {
|
||||
background: linear-gradient(135deg, #1f1f1f 0%, #2a2a2a 100%);
|
||||
border: 1px solid #303030;
|
||||
border-radius: 16px;
|
||||
padding: 16px;
|
||||
margin-bottom: 20px;
|
||||
height: 200px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
color: #ffffff;
|
||||
margin-bottom: 8px;
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
margin-bottom: 12px;
|
||||
min-height: 58px;
|
||||
}
|
||||
|
||||
.text-truncated {
|
||||
font-size: 13px;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
line-height: 1.5;
|
||||
color: #b0b0b0;
|
||||
cursor: pointer;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.text-normal {
|
||||
font-size: 13px;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
line-height: 1.5;
|
||||
color: #b0b0b0;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.info {
|
||||
background: rgba(255,255,255,0.1);
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="test-container">
|
||||
<h1>文本截断测试</h1>
|
||||
|
||||
<div class="info">
|
||||
<h3>测试说明:</h3>
|
||||
<p>• 文本限制为3行显示</p>
|
||||
<p>• 超出3行的文本会显示省略号</p>
|
||||
<p>• 鼠标悬停时会显示完整内容</p>
|
||||
</div>
|
||||
|
||||
<div class="test-card">
|
||||
<div class="title">短文本测试</div>
|
||||
<div class="content">
|
||||
<div class="text-normal" id="short-text">
|
||||
这是一段很短的文本,应该能够完整显示,不需要截断。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-card">
|
||||
<div class="title">中等长度文本测试</div>
|
||||
<div class="content">
|
||||
<div class="text-truncated" id="medium-text" title="这是一段中等长度的文本,可能会超出3行的限制,需要截断显示。如果文本确实被截断了,鼠标悬停时会显示完整内容。">
|
||||
这是一段中等长度的文本,可能会超出3行的限制,需要截断显示。如果文本确实被截断了,鼠标悬停时会显示完整内容。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-card">
|
||||
<div class="title">长文本测试</div>
|
||||
<div class="content">
|
||||
<div class="text-truncated" id="long-text" title="这是一段很长的文本,肯定会超出3行的限制,需要截断显示。这段文本包含了多个句子,用来测试文本截断功能是否正常工作。如果文本确实被截断了,鼠标悬停时会显示完整内容。这样可以确保用户能够看到所有的重要信息。">
|
||||
这是一段很长的文本,肯定会超出3行的限制,需要截断显示。这段文本包含了多个句子,用来测试文本截断功能是否正常工作。如果文本确实被截断了,鼠标悬停时会显示完整内容。这样可以确保用户能够看到所有的重要信息。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-card">
|
||||
<div class="title">超长文本测试</div>
|
||||
<div class="content">
|
||||
<div class="text-truncated" id="very-long-text" title="这是一段超长的文本,绝对会超出3行的限制,需要截断显示。这段文本包含了大量的内容,用来测试文本截断功能在极端情况下的表现。如果文本确实被截断了,鼠标悬停时会显示完整内容。这样可以确保用户能够看到所有的重要信息,同时保持界面的整洁性。文本截断功能应该能够智能地处理各种长度的文本内容。">
|
||||
这是一段超长的文本,绝对会超出3行的限制,需要截断显示。这段文本包含了大量的内容,用来测试文本截断功能在极端情况下的表现。如果文本确实被截断了,鼠标悬停时会显示完整内容。这样可以确保用户能够看到所有的重要信息,同时保持界面的整洁性。文本截断功能应该能够智能地处理各种长度的文本内容。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 检测文本是否被截断
|
||||
function checkTruncation() {
|
||||
const texts = ['short-text', 'medium-text', 'long-text', 'very-long-text'];
|
||||
|
||||
texts.forEach(id => {
|
||||
const element = document.getElementById(id);
|
||||
if (element) {
|
||||
const isTruncated = element.scrollHeight > element.clientHeight;
|
||||
console.log(`${id}:`, {
|
||||
scrollHeight: element.scrollHeight,
|
||||
clientHeight: element.clientHeight,
|
||||
isTruncated: isTruncated
|
||||
});
|
||||
|
||||
// 更新样式
|
||||
if (isTruncated) {
|
||||
element.style.cursor = 'pointer';
|
||||
element.title = element.textContent;
|
||||
} else {
|
||||
element.style.cursor = 'default';
|
||||
element.title = '';
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 页面加载完成后检测
|
||||
window.addEventListener('load', () => {
|
||||
setTimeout(checkTruncation, 100);
|
||||
});
|
||||
|
||||
// 窗口大小改变时重新检测
|
||||
window.addEventListener('resize', () => {
|
||||
setTimeout(checkTruncation, 100);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
测试视频文件访问
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
|
||||
def test_clip_video_access():
|
||||
"""测试切片视频访问"""
|
||||
print("🧪 测试切片视频访问...")
|
||||
|
||||
project_id = "5c48803d-0aa7-48d7-a270-2b33e4954f25"
|
||||
clip_id = "15d2e725-6a8c-4b66-b4d3-f22bbced74db"
|
||||
|
||||
# 1. 获取切片信息
|
||||
try:
|
||||
clips_response = requests.get(f"http://localhost:8000/api/v1/clips/?project_id={project_id}")
|
||||
if clips_response.status_code == 200:
|
||||
clips_data = clips_response.json()
|
||||
clips = clips_data.get('items', [])
|
||||
|
||||
# 找到目标切片
|
||||
target_clip = None
|
||||
for clip in clips:
|
||||
if clip['id'] == clip_id:
|
||||
target_clip = clip
|
||||
break
|
||||
|
||||
if target_clip:
|
||||
print(f"✅ 找到切片: {target_clip['title']}")
|
||||
metadata = target_clip.get('clip_metadata', {})
|
||||
print(f" - ID: {metadata.get('id', '无')}")
|
||||
print(f" - chunk_index: {metadata.get('chunk_index', '无')}")
|
||||
|
||||
# 2. 测试视频URL
|
||||
video_url = f"http://localhost:8000/api/v1/projects/{project_id}/clips/{clip_id}"
|
||||
print(f" - 视频URL: {video_url}")
|
||||
|
||||
video_response = requests.get(video_url)
|
||||
print(f" - 状态码: {video_response.status_code}")
|
||||
|
||||
if video_response.status_code == 200:
|
||||
print("✅ 视频文件访问成功!")
|
||||
print(f" - 内容类型: {video_response.headers.get('content-type', '未知')}")
|
||||
print(f" - 文件大小: {len(video_response.content)} 字节")
|
||||
else:
|
||||
print(f"❌ 视频文件访问失败: {video_response.text}")
|
||||
else:
|
||||
print(f"❌ 未找到切片: {clip_id}")
|
||||
else:
|
||||
print(f"❌ 获取切片列表失败: {clips_response.status_code}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 测试失败: {e}")
|
||||
|
||||
def test_collection_video_access():
|
||||
"""测试合集视频访问"""
|
||||
print("\n🧪 测试合集视频访问...")
|
||||
|
||||
project_id = "5c48803d-0aa7-48d7-a270-2b33e4954f25"
|
||||
|
||||
try:
|
||||
collections_response = requests.get(f"http://localhost:8000/api/v1/collections/?project_id={project_id}")
|
||||
if collections_response.status_code == 200:
|
||||
collections_data = collections_response.json()
|
||||
collections = collections_data.get('items', [])
|
||||
|
||||
if collections:
|
||||
collection = collections[0]
|
||||
collection_id = collection['id']
|
||||
print(f"✅ 找到合集: {collection['name']}")
|
||||
|
||||
# 测试合集视频URL
|
||||
video_url = f"http://localhost:8000/api/v1/collections/{collection_id}/download"
|
||||
print(f" - 视频URL: {video_url}")
|
||||
|
||||
video_response = requests.get(video_url)
|
||||
print(f" - 状态码: {video_response.status_code}")
|
||||
|
||||
if video_response.status_code == 200:
|
||||
print("✅ 合集视频文件访问成功!")
|
||||
else:
|
||||
print(f"❌ 合集视频文件访问失败: {video_response.text}")
|
||||
else:
|
||||
print("❌ 未找到合集")
|
||||
else:
|
||||
print(f"❌ 获取合集列表失败: {collections_response.status_code}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 测试失败: {e}")
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("🚀 开始测试视频文件访问...")
|
||||
|
||||
test_clip_video_access()
|
||||
test_collection_video_access()
|
||||
|
||||
print("\n📊 测试完成")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,272 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
视频处理功能测试脚本
|
||||
用于验证FFmpeg集成和视频切割功能
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import subprocess
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
# 导入视频处理模块
|
||||
try:
|
||||
from backend.utils.video_processor import VideoProcessor
|
||||
from backend.core.shared_config import get_legacy_config
|
||||
print("✅ 成功导入视频处理模块")
|
||||
except ImportError as e:
|
||||
print(f"❌ 导入视频处理模块失败: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
# 配置日志
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def test_ffmpeg_installation():
|
||||
"""测试FFmpeg是否正确安装"""
|
||||
print("\n🔧 测试FFmpeg安装...")
|
||||
|
||||
try:
|
||||
result = subprocess.run(['ffmpeg', '-version'],
|
||||
capture_output=True, text=True, timeout=10)
|
||||
if result.returncode == 0:
|
||||
print("✅ FFmpeg安装正确")
|
||||
print(f" 版本信息: {result.stdout.split('Copyright')[0].strip()}")
|
||||
return True
|
||||
else:
|
||||
print("❌ FFmpeg安装有问题")
|
||||
return False
|
||||
except FileNotFoundError:
|
||||
print("❌ FFmpeg未安装")
|
||||
return False
|
||||
except subprocess.TimeoutExpired:
|
||||
print("❌ FFmpeg响应超时")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"❌ FFmpeg测试失败: {e}")
|
||||
return False
|
||||
|
||||
def test_video_processor():
|
||||
"""测试视频处理器类"""
|
||||
print("\n🎬 测试视频处理器...")
|
||||
|
||||
try:
|
||||
# 创建视频处理器实例
|
||||
processor = VideoProcessor()
|
||||
print("✅ 视频处理器创建成功")
|
||||
|
||||
# 测试文件名清理
|
||||
test_filename = "测试文件<>:\"/\\|?*名称.mp4"
|
||||
cleaned = VideoProcessor.sanitize_filename(test_filename)
|
||||
print(f"✅ 文件名清理测试: '{test_filename}' -> '{cleaned}'")
|
||||
|
||||
# 测试时间格式转换
|
||||
srt_time = "00:01:30,500"
|
||||
ffmpeg_time = VideoProcessor.convert_srt_time_to_ffmpeg_time(srt_time)
|
||||
print(f"✅ 时间格式转换: '{srt_time}' -> '{ffmpeg_time}'")
|
||||
|
||||
# 测试秒数转换
|
||||
seconds = 90.5
|
||||
time_str = VideoProcessor.convert_seconds_to_ffmpeg_time(seconds)
|
||||
print(f"✅ 秒数转换: {seconds}s -> '{time_str}'")
|
||||
|
||||
# 测试时间解析
|
||||
parsed_seconds = VideoProcessor.convert_ffmpeg_time_to_seconds(time_str)
|
||||
print(f"✅ 时间解析: '{time_str}' -> {parsed_seconds}s")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 视频处理器测试失败: {e}")
|
||||
return False
|
||||
|
||||
def test_video_extraction():
|
||||
"""测试视频切割功能"""
|
||||
print("\n✂️ 测试视频切割功能...")
|
||||
|
||||
# 检查是否有测试视频文件
|
||||
config = get_legacy_config()
|
||||
test_video_path = config['INPUT_VIDEO']
|
||||
|
||||
if not test_video_path.exists():
|
||||
print(f"⚠️ 测试视频文件不存在: {test_video_path}")
|
||||
print(" 请将测试视频文件放在 input/input.mp4")
|
||||
return False
|
||||
|
||||
try:
|
||||
# 创建输出目录
|
||||
output_dir = Path("test_output")
|
||||
output_dir.mkdir(exist_ok=True)
|
||||
|
||||
# 测试视频信息获取
|
||||
video_info = VideoProcessor.get_video_info(test_video_path)
|
||||
if video_info:
|
||||
print(f"✅ 获取视频信息成功: 时长={video_info.get('duration', 'N/A')}秒")
|
||||
else:
|
||||
print("❌ 获取视频信息失败")
|
||||
return False
|
||||
|
||||
# 测试视频切割
|
||||
output_path = output_dir / "test_clip.mp4"
|
||||
start_time = "00:00:10"
|
||||
end_time = "00:00:20"
|
||||
|
||||
print(f" 切割视频: {start_time} -> {end_time}")
|
||||
success = VideoProcessor.extract_clip(test_video_path, output_path, start_time, end_time)
|
||||
|
||||
if success and output_path.exists():
|
||||
print(f"✅ 视频切割成功: {output_path}")
|
||||
# 清理测试文件
|
||||
output_path.unlink()
|
||||
return True
|
||||
else:
|
||||
print("❌ 视频切割失败")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 视频切割测试失败: {e}")
|
||||
return False
|
||||
|
||||
def test_batch_processing():
|
||||
"""测试批量处理功能"""
|
||||
print("\n📦 测试批量处理功能...")
|
||||
|
||||
config = get_legacy_config()
|
||||
test_video_path = config['INPUT_VIDEO']
|
||||
|
||||
if not test_video_path.exists():
|
||||
print("⚠️ 跳过批量处理测试(无测试视频)")
|
||||
return True
|
||||
|
||||
try:
|
||||
# 创建测试数据
|
||||
test_clips = [
|
||||
{
|
||||
'id': 'test_001',
|
||||
'title': '测试片段1',
|
||||
'start_time': 10, # 秒数
|
||||
'end_time': 20
|
||||
},
|
||||
{
|
||||
'id': 'test_002',
|
||||
'title': '测试片段2',
|
||||
'start_time': 30,
|
||||
'end_time': 40
|
||||
}
|
||||
]
|
||||
|
||||
# 创建输出目录
|
||||
clips_dir = Path("test_output/clips")
|
||||
clips_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
processor = VideoProcessor(clips_dir=str(clips_dir))
|
||||
|
||||
print(f" 批量处理 {len(test_clips)} 个片段...")
|
||||
successful_clips = processor.batch_extract_clips(test_video_path, test_clips)
|
||||
|
||||
print(f"✅ 批量处理完成: {len(successful_clips)}/{len(test_clips)} 成功")
|
||||
|
||||
# 清理测试文件
|
||||
for clip_path in successful_clips:
|
||||
clip_path.unlink()
|
||||
|
||||
return len(successful_clips) > 0
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 批量处理测试失败: {e}")
|
||||
return False
|
||||
|
||||
def test_collection_creation():
|
||||
"""测试合集创建功能"""
|
||||
print("\n🎭 测试合集创建功能...")
|
||||
|
||||
try:
|
||||
# 创建测试数据
|
||||
test_collections = [
|
||||
{
|
||||
'id': 'collection_001',
|
||||
'collection_title': '测试合集1',
|
||||
'clip_ids': ['test_001', 'test_002']
|
||||
}
|
||||
]
|
||||
|
||||
# 创建输出目录
|
||||
collections_dir = Path("test_output/collections")
|
||||
collections_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
processor = VideoProcessor(collections_dir=str(collections_dir))
|
||||
|
||||
print(f" 创建 {len(test_collections)} 个合集...")
|
||||
successful_collections = processor.create_collections_from_metadata(test_collections)
|
||||
|
||||
print(f"✅ 合集创建完成: {len(successful_collections)} 成功")
|
||||
|
||||
# 清理测试文件
|
||||
for collection_path in successful_collections:
|
||||
collection_path.unlink()
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 合集创建测试失败: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
"""主测试函数"""
|
||||
print("🚀 开始视频处理功能测试")
|
||||
print("=" * 50)
|
||||
|
||||
tests = [
|
||||
("FFmpeg安装", test_ffmpeg_installation),
|
||||
("视频处理器", test_video_processor),
|
||||
("视频切割", test_video_extraction),
|
||||
("批量处理", test_batch_processing),
|
||||
("合集创建", test_collection_creation)
|
||||
]
|
||||
|
||||
results = []
|
||||
|
||||
for test_name, test_func in tests:
|
||||
try:
|
||||
result = test_func()
|
||||
results.append((test_name, result))
|
||||
except Exception as e:
|
||||
print(f"❌ {test_name}测试异常: {e}")
|
||||
results.append((test_name, False))
|
||||
|
||||
# 输出测试结果
|
||||
print("\n" + "=" * 50)
|
||||
print("📊 测试结果汇总:")
|
||||
|
||||
passed = 0
|
||||
total = len(results)
|
||||
|
||||
for test_name, result in results:
|
||||
status = "✅ 通过" if result else "❌ 失败"
|
||||
print(f" {test_name}: {status}")
|
||||
if result:
|
||||
passed += 1
|
||||
|
||||
print(f"\n🎯 总体结果: {passed}/{total} 测试通过")
|
||||
|
||||
if passed == total:
|
||||
print("🎉 所有测试通过!视频处理功能正常")
|
||||
return 0
|
||||
else:
|
||||
print("⚠️ 部分测试失败,请检查相关功能")
|
||||
return 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit_code = main()
|
||||
sys.exit(exit_code)
|
||||
|
||||
@@ -1,214 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
更新切片元数据
|
||||
更新数据库中现有切片的clip_metadata字段,添加完整的元数据
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
# 设置环境变量
|
||||
import os
|
||||
os.environ['PYTHONPATH'] = str(project_root)
|
||||
|
||||
# 添加backend目录到路径
|
||||
backend_dir = project_root / "backend"
|
||||
sys.path.insert(0, str(backend_dir))
|
||||
|
||||
from backend.core.database import SessionLocal
|
||||
from backend.models.clip import Clip
|
||||
from backend.models.collection import Collection
|
||||
|
||||
# 配置日志
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def update_clip_metadata(project_id: str):
|
||||
"""更新项目切片的元数据"""
|
||||
print(f"🔧 更新项目切片元数据: {project_id}")
|
||||
|
||||
try:
|
||||
db = SessionLocal()
|
||||
|
||||
try:
|
||||
# 获取项目的所有切片
|
||||
clips = db.query(Clip).filter(Clip.project_id == project_id).all()
|
||||
print(f"📊 找到 {len(clips)} 个切片")
|
||||
|
||||
updated_count = 0
|
||||
|
||||
for clip in clips:
|
||||
try:
|
||||
# 获取元数据文件路径
|
||||
metadata_file = clip.clip_metadata.get('metadata_file') if clip.clip_metadata else None
|
||||
|
||||
if not metadata_file or not Path(metadata_file).exists():
|
||||
print(f"⚠️ 切片 {clip.id} 的元数据文件不存在: {metadata_file}")
|
||||
continue
|
||||
|
||||
# 读取元数据文件
|
||||
with open(metadata_file, 'r', encoding='utf-8') as f:
|
||||
metadata_data = json.load(f)
|
||||
|
||||
# 更新clip_metadata字段
|
||||
updated_metadata = {
|
||||
'metadata_file': metadata_file,
|
||||
'clip_id': clip.id,
|
||||
'created_at': clip.clip_metadata.get('created_at', ''),
|
||||
# 添加完整的元数据字段
|
||||
'recommend_reason': metadata_data.get('recommend_reason', ''),
|
||||
'outline': metadata_data.get('outline', ''),
|
||||
'content': metadata_data.get('content', []),
|
||||
'chunk_index': metadata_data.get('chunk_index', 0),
|
||||
'generated_title': metadata_data.get('generated_title', ''),
|
||||
'id': metadata_data.get('id', '') # 添加id字段
|
||||
}
|
||||
|
||||
# 更新数据库
|
||||
clip.clip_metadata = updated_metadata
|
||||
updated_count += 1
|
||||
|
||||
print(f"✅ 更新切片 {clip.id}: {clip.title}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 更新切片 {clip.id} 失败: {e}")
|
||||
continue
|
||||
|
||||
# 提交更改
|
||||
db.commit()
|
||||
print(f"✅ 成功更新 {updated_count}/{len(clips)} 个切片的元数据")
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 更新失败: {e}")
|
||||
|
||||
def update_collection_metadata(project_id: str):
|
||||
"""更新项目合集的元数据"""
|
||||
print(f"🔧 更新项目合集元数据: {project_id}")
|
||||
|
||||
try:
|
||||
db = SessionLocal()
|
||||
|
||||
try:
|
||||
# 获取项目的所有合集
|
||||
collections = db.query(Collection).filter(Collection.project_id == project_id).all()
|
||||
print(f"📊 找到 {len(collections)} 个合集")
|
||||
|
||||
updated_count = 0
|
||||
|
||||
for collection in collections:
|
||||
try:
|
||||
# 获取元数据文件路径
|
||||
metadata_file = collection.collection_metadata.get('metadata_file') if collection.collection_metadata else None
|
||||
|
||||
if not metadata_file or not Path(metadata_file).exists():
|
||||
print(f"⚠️ 合集 {collection.id} 的元数据文件不存在: {metadata_file}")
|
||||
continue
|
||||
|
||||
# 读取元数据文件
|
||||
with open(metadata_file, 'r', encoding='utf-8') as f:
|
||||
metadata_data = json.load(f)
|
||||
|
||||
# 更新collection_metadata字段
|
||||
updated_metadata = {
|
||||
'metadata_file': metadata_file,
|
||||
'clip_ids': metadata_data.get('clip_ids', []),
|
||||
'collection_type': 'ai_recommended',
|
||||
'collection_id': collection.id,
|
||||
'created_at': collection.collection_metadata.get('created_at', ''),
|
||||
# 添加完整的元数据字段
|
||||
'collection_title': metadata_data.get('collection_title', ''),
|
||||
'collection_summary': metadata_data.get('collection_summary', '')
|
||||
}
|
||||
|
||||
# 更新数据库
|
||||
collection.collection_metadata = updated_metadata
|
||||
updated_count += 1
|
||||
|
||||
print(f"✅ 更新合集 {collection.id}: {collection.name}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 更新合集 {collection.id} 失败: {e}")
|
||||
continue
|
||||
|
||||
# 提交更改
|
||||
db.commit()
|
||||
print(f"✅ 成功更新 {updated_count}/{len(collections)} 个合集的元数据")
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 更新失败: {e}")
|
||||
|
||||
def test_api_response(project_id: str):
|
||||
"""测试API响应"""
|
||||
print(f"🧪 测试API响应: {project_id}")
|
||||
|
||||
try:
|
||||
import requests
|
||||
|
||||
# 测试切片API
|
||||
clips_response = requests.get(f"http://localhost:8000/api/v1/clips/?project_id={project_id}")
|
||||
if clips_response.status_code == 200:
|
||||
clips_data = clips_response.json()
|
||||
print(f"✅ 切片API返回 {len(clips_data['items'])} 个切片")
|
||||
|
||||
if clips_data['items']:
|
||||
first_clip = clips_data['items'][0]
|
||||
metadata = first_clip.get('clip_metadata', {})
|
||||
print(f" 第一个切片的元数据:")
|
||||
print(f" - recommend_reason: {metadata.get('recommend_reason', '无')}")
|
||||
print(f" - outline: {metadata.get('outline', '无')}")
|
||||
print(f" - content: {len(metadata.get('content', []))} 个要点")
|
||||
|
||||
# 测试合集API
|
||||
collections_response = requests.get(f"http://localhost:8000/api/v1/collections/?project_id={project_id}")
|
||||
if collections_response.status_code == 200:
|
||||
collections_data = collections_response.json()
|
||||
print(f"✅ 合集API返回 {len(collections_data['items'])} 个合集")
|
||||
|
||||
if collections_data['items']:
|
||||
first_collection = collections_data['items'][0]
|
||||
metadata = first_collection.get('collection_metadata', {})
|
||||
print(f" 第一个合集的元数据:")
|
||||
print(f" - collection_title: {metadata.get('collection_title', '无')}")
|
||||
print(f" - collection_summary: {metadata.get('collection_summary', '无')}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ API测试失败: {e}")
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="更新切片和合集元数据")
|
||||
parser.add_argument("--project-id", type=str, required=True, help="项目ID")
|
||||
parser.add_argument("--test-only", action="store_true", help="仅测试API响应")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
project_id = args.project_id
|
||||
|
||||
if args.test_only:
|
||||
# 仅测试API响应
|
||||
test_api_response(project_id)
|
||||
else:
|
||||
# 更新元数据并测试
|
||||
print("🔧 开始更新元数据...")
|
||||
update_clip_metadata(project_id)
|
||||
update_collection_metadata(project_id)
|
||||
|
||||
print("\n🧪 测试API响应...")
|
||||
test_api_response(project_id)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user