Files
autoclip/backend/pipeline/step6_video.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

185 lines
7.0 KiB
Python

"""
Step 6: 视频生成 - 根据聚类结果生成最终视频切片
"""
import json
import logging
import re
from typing import List, Dict, Any, Optional
from pathlib import Path
# 导入依赖
from ..utils.video_processor import VideoProcessor
from ..core.shared_config import METADATA_DIR, CLIPS_DIR, COLLECTIONS_DIR
logger = logging.getLogger(__name__)
class VideoGenerator:
"""视频生成器"""
def __init__(self, clips_dir: Optional[str] = None, collections_dir: Optional[str] = None, metadata_dir: Optional[str] = None):
self.clips_dir = Path(clips_dir) if clips_dir else CLIPS_DIR
self.collections_dir = Path(collections_dir) if collections_dir else COLLECTIONS_DIR
self.metadata_dir = Path(metadata_dir) if metadata_dir else METADATA_DIR
self.video_processor = VideoProcessor(clips_dir=str(self.clips_dir), collections_dir=str(self.collections_dir))
def generate_clips(self, clips_with_titles: List[Dict], input_video: Path) -> List[Path]:
"""
生成切片视频
Args:
clips_with_titles: 带标题的片段数据
input_video: 输入视频路径
Returns:
生成的切片视频路径列表
"""
logger.info("开始生成切片视频...")
# 准备切片数据
clips_data = []
for clip in clips_with_titles:
clips_data.append({
'id': clip['id'],
'title': clip.get('generated_title', f"片段_{clip['id']}"),
'start_time': clip['start_time'],
'end_time': clip['end_time']
})
# 批量生成切片
successful_clips = self.video_processor.batch_extract_clips(input_video, clips_data)
logger.info(f"切片视频生成完成,共{len(successful_clips)}个切片")
return successful_clips
def generate_collections(self, collections_data: List[Dict]) -> List[Path]:
"""
生成合集视频
Args:
collections_data: 合集数据
Returns:
生成的合集视频路径列表
"""
logger.info("开始生成合集视频...")
# 生成合集视频
successful_collections = self.video_processor.create_collections_from_metadata(collections_data)
logger.info(f"合集视频生成完成,共{len(successful_collections)}个合集")
return successful_collections
def save_clip_metadata(self, clips_with_titles: List[Dict], output_path: Optional[Path] = None) -> Path:
"""
保存最终的切片元数据到clips_metadata.json
Args:
clips_with_titles: 带标题的片段数据(来自step4)
output_path: 输出路径,默认为clips_metadata.json
Returns:
保存的文件路径
Note:
此方法保存的是最终的切片元数据,包含视频生成后的完整信息。
与step4的step4_titles.json不同,这里保存的是用于前端展示的最终数据。
"""
if output_path is None:
output_path = self.metadata_dir / "clips_metadata.json"
# 确保目录存在
output_path.parent.mkdir(parents=True, exist_ok=True)
# 保存数据
with open(output_path, 'w', encoding='utf-8') as f:
json.dump(clips_with_titles, f, ensure_ascii=False, indent=2)
logger.info(f"切片元数据已保存到: {output_path}")
return output_path
def save_collection_metadata(self, collections_data: List[Dict], output_path: Optional[Path] = None) -> Path:
"""
保存合集元数据
Args:
collections_data: 合集数据
output_path: 输出路径
Returns:
保存的文件路径
"""
if output_path is None:
output_path = self.metadata_dir / "collections_metadata.json"
# 确保目录存在
output_path.parent.mkdir(parents=True, exist_ok=True)
# 保存数据
with open(output_path, 'w', encoding='utf-8') as f:
json.dump(collections_data, f, ensure_ascii=False, indent=2)
logger.info(f"合集元数据已保存到: {output_path}")
return output_path
def run_step6_video(clips_with_titles_path: Path, collections_path: Path,
input_video: Path, output_dir: Optional[Path] = None,
clips_dir: Optional[str] = None, collections_dir: Optional[str] = None,
metadata_dir: Optional[str] = None) -> Dict:
"""
运行Step 6: 视频切割
Args:
clips_with_titles_path: 带标题的片段文件路径
collections_path: 合集文件路径
input_video: 输入视频路径
output_dir: 输出目录
Returns:
生成结果信息
"""
# 加载数据
with open(clips_with_titles_path, 'r', encoding='utf-8') as f:
clips_with_titles = json.load(f)
with open(collections_path, 'r', encoding='utf-8') as f:
collections_data = json.load(f)
# 创建视频生成器
generator = VideoGenerator(clips_dir=clips_dir, collections_dir=collections_dir, metadata_dir=metadata_dir)
# 生成切片视频
successful_clips = generator.generate_clips(clips_with_titles, input_video)
# 生成合集视频
successful_collections = generator.generate_collections(collections_data)
# 保存元数据到项目目录
# 注意:clips_metadata.json在这里保存,包含最终的切片元数据(包含视频路径等信息)
# 这与step4的step4_titles.json不同,step4只保存带标题的片段数据
if metadata_dir:
project_metadata_dir = Path(metadata_dir)
generator.save_clip_metadata(clips_with_titles, project_metadata_dir / "clips_metadata.json")
generator.save_collection_metadata(collections_data, project_metadata_dir / "collections_metadata.json")
else:
generator.save_clip_metadata(clips_with_titles)
generator.save_collection_metadata(collections_data)
# 返回结果信息
result = {
'clips_generated': len(successful_clips),
'collections_generated': len(successful_collections),
'clip_paths': [str(path) for path in successful_clips],
'collection_paths': [str(path) for path in successful_collections]
}
logger.info(f"视频生成完成: {result['clips_generated']}个切片, {result['collections_generated']}个合集")
# 保存结果到输出文件
if output_dir is not None:
output_path = output_dir / "step6_video_output.json"
output_path.parent.mkdir(parents=True, exist_ok=True)
with open(output_path, 'w', encoding='utf-8') as f:
json.dump(result, f, ensure_ascii=False, indent=2)
logger.info(f"步骤6结果已保存到: {output_path}")
return result