修复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:
Kris Ka
2025-09-08 17:21:31 +08:00
parent 45f6ae5e21
commit 92c2e8ede7
204 changed files with 15894 additions and 13466 deletions

View File

@@ -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个切片
## 如果仍然有问题
请提供控制台的完整日志输出,特别是任何错误信息。

View File

@@ -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)

View File

@@ -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()

View File

@@ -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()

View File

@@ -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✅ 清理和验证完成!")

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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())

View File

@@ -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())

View File

@@ -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()

View File

@@ -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()

View File

@@ -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("❌ 修复失败")

View File

@@ -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_idsmetadata_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()

View File

@@ -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()

View File

@@ -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> # 迁移指定项目")

View File

@@ -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()

View File

@@ -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()

View File

@@ -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)

View File

@@ -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()

View File

@@ -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> # 同步特定项目")

View File

@@ -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())

View File

@@ -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🎉 测试完成!")

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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())

View File

@@ -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()

View File

@@ -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>

View File

@@ -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()

View File

@@ -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);
});

View File

@@ -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()

View File

@@ -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()

View File

@@ -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())

View File

@@ -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()

View File

@@ -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())

View File

@@ -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())

View File

@@ -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())

View File

@@ -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())

View File

@@ -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)

View File

@@ -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>

View File

@@ -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()

View File

@@ -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)

View File

@@ -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()