Files
autoclip/backend/services/auto_pipeline_service.py
T
Kris Ka 8ee2d50044 修复WebSocket进度更新和前端状态自动更新问题
- 修复WebSocketNotificationService.send_processing_progress方法参数不匹配问题
- 修复前端RealTimeStatus组件WebSocket消息处理逻辑
- 修复Celery Worker队列配置,确保任务正确路由到processing队列
- 修复Celery应用导入冲突,统一使用正确的celery_app配置
- 添加实时项目状态更新功能,前端无需手动刷新即可看到处理进度
- 完善系统启动脚本,修复PYTHONPATH未绑定变量错误
- 优化流水线处理逻辑,确保所有6个步骤正常执行
- 添加完整的项目文档和启动指南

测试结果:
- WebSocket进度更新正常工作(16%, 33%, 100%)
- 流水线处理完全正常(6个步骤全部成功)
- 前端状态自动更新正常
- 项目状态正确同步到数据库
2025-09-08 17:21:31 +08:00

297 lines
12 KiB
Python

"""
自动化流水线启动服务
当新项目创建后自动启动视频处理流水线
"""
import logging
import asyncio
from typing import Dict, Any, Optional
from datetime import datetime
from pathlib import Path
from sqlalchemy.orm import Session
from backend.core.database import SessionLocal
from backend.models.project import Project, ProjectStatus
from backend.models.task import Task, TaskStatus
from backend.services.progress_update_service import progress_update_service
# from backend.services.pipeline_adapter import PipelineAdapter # 临时注释,文件不存在
from backend.utils.task_submission_utils import submit_video_pipeline_task
logger = logging.getLogger(__name__)
class AutoPipelineService:
"""自动化流水线启动服务"""
def __init__(self):
self.processing_projects = set()
async def auto_start_pipeline(self, project_id: str) -> Dict[str, Any]:
"""
自动启动项目流水线处理
Args:
project_id: 项目ID
Returns:
启动结果
"""
try:
logger.info(f"自动启动项目流水线: {project_id}")
# 检查项目是否已经在处理中
if project_id in self.processing_projects:
logger.warning(f"项目 {project_id} 已在处理中,跳过")
return {"status": "skipped", "message": "项目已在处理中"}
# 标记项目为处理中
self.processing_projects.add(project_id)
# 获取项目信息
db = SessionLocal()
try:
project = db.query(Project).filter(Project.id == project_id).first()
if not project:
raise ValueError(f"项目 {project_id} 不存在")
# 检查项目状态
if project.status != ProjectStatus.PENDING:
logger.info(f"项目 {project_id} 状态为 {project.status},跳过自动启动")
return {"status": "skipped", "message": f"项目状态为 {project.status}"}
# 检查项目文件
if not project.video_path:
raise ValueError(f"项目 {project_id} 没有视频文件")
# 查找字幕文件
srt_file = self._find_srt_file(project_id)
if not srt_file:
logger.warning(f"项目 {project_id} 没有找到字幕文件,将尝试自动生成")
# 检查是否已有正在运行的任务
existing_task = db.query(Task).filter(
Task.project_id == project_id,
Task.name == "自动视频处理流水线",
Task.status.in_([TaskStatus.PENDING, TaskStatus.RUNNING])
).first()
if existing_task:
# 使用现有任务
task = existing_task
logger.info(f"使用现有任务: {task.id}")
else:
# 创建新任务记录
task = self._create_processing_task(db, project_id)
if not task:
raise ValueError("创建任务记录失败")
# 更新项目状态
project.status = ProjectStatus.PROCESSING
project.updated_at = datetime.utcnow()
db.commit()
logger.info(f"项目 {project_id} 状态已更新为处理中")
# 启动进度监控
await progress_update_service.start_progress_monitoring(task.id)
# 提交Celery任务
logger.info(f"准备提交Celery任务: {project_id}")
# 查找项目文件路径
from ..core.config import get_data_directory
data_dir = get_data_directory()
project_dir = Path(data_dir) / "projects" / project_id
input_video_path = str(project_dir / "raw" / "input.mp4")
input_srt_path = str(project_dir / "raw" / "input.srt")
# 检查文件是否存在
if not Path(input_video_path).exists():
raise ValueError(f"视频文件不存在: {input_video_path}")
logger.info(f"视频文件: {input_video_path}")
logger.info(f"字幕文件: {input_srt_path if Path(input_srt_path).exists() else '不存在'}")
# 提交Celery任务
task_result = submit_video_pipeline_task(project_id, input_video_path, input_srt_path)
logger.info(f"Celery任务提交结果: {task_result}")
if task_result.get('success'):
celery_task_id = task_result['task_id']
logger.info(f"Celery任务已提交: {celery_task_id}")
# 更新任务记录
task.celery_task_id = celery_task_id
task.status = TaskStatus.RUNNING
task.started_at = datetime.utcnow()
db.commit()
result = {
"status": "started",
"message": "流水线处理已启动",
"project_id": project_id,
"task_id": task.id,
"celery_task_id": celery_task_id
}
else:
error_msg = task_result.get('error', '未知错误')
raise ValueError(f"提交Celery任务失败: {error_msg}")
return result
finally:
db.close()
except Exception as e:
logger.error(f"自动启动流水线失败: {e}")
# 移除处理中标记
self.processing_projects.discard(project_id)
# 更新项目状态为失败
await self._mark_project_failed(project_id, str(e))
return {"status": "failed", "message": f"启动失败: {str(e)}"}
def _find_srt_file(self, project_id: str) -> Optional[str]:
"""查找项目的字幕文件"""
try:
from ..core.path_utils import get_project_directory
project_dir = get_project_directory(project_id)
# 查找可能的字幕文件
srt_files = list(project_dir.glob("**/*.srt"))
if srt_files:
return str(srt_files[0])
# 查找原始目录
raw_dir = project_dir / "raw"
if raw_dir.exists():
srt_files = list(raw_dir.glob("*.srt"))
if srt_files:
return str(srt_files[0])
return None
except Exception as e:
logger.warning(f"查找字幕文件失败: {e}")
return None
def _create_processing_task(self, db: Session, project_id: str) -> Optional[Task]:
"""创建处理任务记录"""
try:
task = Task(
project_id=project_id,
name="自动视频处理流水线",
task_type="video_processing",
status=TaskStatus.PENDING,
progress=0.0,
current_step="初始化",
priority=0,
metadata={
"auto_started": True,
"started_at": datetime.utcnow().isoformat()
}
)
db.add(task)
db.commit()
db.refresh(task)
logger.info(f"创建处理任务: {task.id}")
return task
except Exception as e:
logger.error(f"创建任务记录失败: {e}")
db.rollback()
return None
async def _mark_project_failed(self, project_id: str, error_message: str):
"""标记项目为失败状态"""
try:
db = SessionLocal()
try:
project = db.query(Project).filter(Project.id == project_id).first()
if project:
project.status = ProjectStatus.FAILED
project.updated_at = datetime.utcnow()
db.commit()
logger.info(f"项目 {project_id} 已标记为失败")
# 从处理中项目集合中移除
self.processing_projects.discard(project_id)
logger.info(f"项目 {project_id} 已从处理中集合移除")
finally:
db.close()
except Exception as e:
logger.error(f"标记项目失败状态时出错: {e}")
async def check_and_restart_failed_pipelines(self):
"""检查并重启失败的流水线"""
try:
db = SessionLocal()
try:
# 查找失败的项目
failed_projects = db.query(Project).filter(
Project.status == ProjectStatus.FAILED
).all()
for project in failed_projects:
logger.info(f"检查失败项目: {project.id}")
# 检查是否有未完成的任务
incomplete_tasks = db.query(Task).filter(
Task.project_id == project.id,
Task.status.in_([TaskStatus.PENDING, TaskStatus.RUNNING])
).all()
if not incomplete_tasks:
logger.info(f"项目 {project.id} 没有未完成任务,尝试重启")
await self.auto_start_pipeline(project.id)
else:
logger.info(f"项目 {project.id} 仍有未完成任务,跳过重启")
finally:
db.close()
except Exception as e:
logger.error(f"检查失败流水线时出错: {e}")
async def auto_start_all_pending_pipelines(self):
"""自动启动所有等待中的流水线"""
try:
db = SessionLocal()
try:
# 查找所有等待中的项目
pending_projects = db.query(Project).filter(
Project.status == ProjectStatus.PENDING
).all()
logger.info(f"找到 {len(pending_projects)} 个等待中的项目")
for project in pending_projects:
try:
logger.info(f"自动启动项目流水线: {project.id}")
result = await self.auto_start_pipeline(project.id)
logger.info(f"项目 {project.id} 启动结果: {result}")
# 避免同时启动太多项目
await asyncio.sleep(1)
except Exception as e:
logger.error(f"自动启动项目 {project.id} 失败: {e}")
continue
finally:
db.close()
except Exception as e:
logger.error(f"自动启动所有等待中流水线时出错: {e}")
def get_processing_status(self) -> Dict[str, Any]:
"""获取处理状态"""
return {
"processing_projects": list(self.processing_projects),
"total_processing": len(self.processing_projects)
}
# 全局实例
auto_pipeline_service = AutoPipelineService()