"""语义分段引擎 使用 LLM 实现智能视频字幕语义分段。 功能: - 视频类型识别(知识类/故事类/访谈类/新闻类/vlog 等) - 语义边界检测 - 内容完整性保护 - 主题标题生成 """ from __future__ import annotations import json import logging import re from typing import List, Optional, Protocol from pydantic import BaseModel, Field, field_validator, ValidationError logger = logging.getLogger(__name__) # ============================================================================ # AI Client Protocol # ============================================================================ class AIClientProtocol(Protocol): """AI 客户端协议(与 ASRCorrector 保持一致)""" def generate(self, prompt: str, max_tokens: int = 16000, disable_reasoning: bool = False) -> str: """生成文本""" ... # ============================================================================ # Pydantic Models # ============================================================================ class VideoSegment(BaseModel): """视频分段 Attributes: title: 分段标题(可选) content: 分段内容(必填) start_index: 起始字幕索引 end_index: 结束字幕索引 """ title: Optional[str] = Field( None, description="分段标题,简洁描述型(5-15字)" ) content: str = Field( ..., description="分段内容,合并后的字幕文本" ) start_index: int = Field( ..., ge=1, description="起始字幕索引(从1开始)" ) end_index: int = Field( ..., ge=1, description="结束字幕索引" ) @field_validator('title') @classmethod def validate_title_length(cls, v: Optional[str]) -> Optional[str]: """验证标题长度(5-15字)""" if v is not None and len(v) > 15: raise ValueError(f"标题长度不能超过15字,当前为{len(v)}字") return v model_config = { "json_schema_extra": { "examples": [ { "title": "机器学习概述", "content": "机器学习是人工智能的一个分支...", "start_index": 1, "end_index": 10 } ] } } class SegmentationResult(BaseModel): """分段结果 Attributes: video_type: 视频类型(知识类/故事类/访谈类/新闻类/vlog 等) segments: 分段列表 actual_segment_count: 实际分段数量 """ video_type: str = Field( ..., description="识别出的视频类型" ) segments: List[VideoSegment] = Field( default_factory=list, description="分段列表" ) actual_segment_count: int = Field( ..., ge=0, description="实际分段数量" ) model_config = { "json_schema_extra": { "examples": [ { "video_type": "知识类", "segments": [ { "title": "机器学习概述", "content": "...", "start_index": 1, "end_index": 10 } ], "actual_segment_count": 5 } ] } } # ============================================================================ # Video Type Detection # ============================================================================ # 视频类型关键词映射 VIDEO_TYPE_KEYWORDS = { "知识类": [ "教程", "课程", "教学", "入门", "基础", "进阶", "实战", "编程", "开发", "技术", "学习", "教育", "培训", "讲座" ], "故事类": [ "影视", "电影", "电视剧", "动漫", "动画", "小说", "故事", "剧情", "短片", "纪录片" ], "访谈类": [ "访谈", "对话", "采访", "专访", "聊天", "脱口秀", "播客" ], "新闻类": [ "新闻", "报道", "资讯", "时事", "热点", "快讯" ], "vlog": [ "vlog", "日常", "生活", "旅行", "美食", "探店", "分享", "体验" ] } # 默认视频类型 DEFAULT_VIDEO_TYPE = "知识类" # ============================================================================ # Semantic Segmentation Engine # ============================================================================ class SemanticSegmentationEngine: """语义分段引擎 使用 LLM 实现智能视频字幕语义分段。 功能: - 识别视频类型(从元数据推断) - 构建包含类型信息的 Prompt - 调用 LLM 进行语义分段 - 验证和返回分段结果 Attributes: ai_client: AI 客户端(可选,暂未实现完整 LLM 调用) """ def __init__(self, ai_client: Optional[object] = None): """初始化语义分段引擎 Args: ai_client: AI 客户端实例(预留,后续计划实现) """ self.ai_client = ai_client def _infer_video_type(self, metadata: dict) -> str: """从元数据推断视频类型 Args: metadata: 视频元数据,包含 title, category, tags 等字段 Returns: 推断出的视频类型,默认为"知识类" Examples: >>> engine = SemanticSegmentationEngine() >>> engine._infer_video_type({"title": "Python教程"}) '知识类' >>> engine._infer_video_type({"category": "影视"}) '故事类' """ if not metadata: return DEFAULT_VIDEO_TYPE # 提取所有文本字段 title = metadata.get("title", "") or "" category = metadata.get("category", "") or "" tags = metadata.get("tags", []) or [] description = metadata.get("description", "") or "" # 合并所有文本用于匹配 all_text = " ".join([title, category, description] + list(tags)) # 如果没有有效文本,返回默认类型 if not all_text or all_text.isspace(): return DEFAULT_VIDEO_TYPE # 按优先级匹配类型关键词 for video_type, keywords in VIDEO_TYPE_KEYWORDS.items(): for keyword in keywords: if keyword in all_text: logger.debug( f"Detected video type '{video_type}' " f"from keyword '{keyword}'" ) return video_type # 未匹配到任何类型,返回默认 logger.debug("No specific video type detected, using default") return DEFAULT_VIDEO_TYPE def _build_prompt(self, content: str, metadata: dict) -> str: """构建语义分段 Prompt Args: content: 字幕内容 metadata: 视频元数据 Returns: 构建好的 Prompt 字符串 Examples: >>> engine = SemanticSegmentationEngine() >>> prompt = engine._build_prompt("字幕内容", {"title": "教程"}) >>> "视频类型" in prompt or "知识类" in prompt True """ # 推断视频类型 video_type = self._infer_video_type(metadata) title = metadata.get("title", "") or "" # 构建 Prompt total_chars = len(content) prompt = f"""你是一个专业的视频内容分析师。请将以下字幕内容按语义分段。 【视频信息】 类型:{video_type} 标题:{title} 总字数:{total_chars}字 【字幕内容】 {content} 【分段要求】 1. 建议将内容分为 5-8 个段落(可根据语义完整性灵活调整) 2. 每个段落约 1000-2000 字(为保持语义完整性可以突破此范围) 3. 在话题转换处创建新分段,不要混入不同话题的内容 4. **重要**:保持句子完整性,分段边界应放在自然停顿处(句号。、问号?、感叹号!、省略号……等标点符号后) - 基于语义停顿判断,不仅依赖标点符号 - 保持内容的连贯性和可读性 5. 保持论点完整性,不要在前提-结论结构中间截断 6. 为主题/章节类型的分段生成简洁标题(5-15字),论点/观点类型分段不加标题 【输出格式】 必须返回以下 JSON 格式(不要添加任何额外文本): ```json {{ "video_type": "{video_type}", "segments": [ {{ "title": "分段标题(如果此分段不需要标题则设为 null)", "content": "分段内容", "start_index": 0, "end_index": 123 }} ], "actual_segment_count": 6 }} ``` 【注意事项】 - 优先保证语义完整性,不要为了凑数而强行截断 - 标题应简洁准确,如「机器学习概述」「梯度下降原理」 - 如果某个分段无法生成合适的标题(如过渡性内容),将 title 设为 null""" return prompt def segment( self, subtitles: List[dict], metadata: dict ) -> SegmentationResult: """执行语义分段(占位方法,后续计划实现) Args: subtitles: 字幕列表,每个元素包含 index, start, end, text metadata: 视频元数据 Returns: 分段结果 """ # 暂时返回默认结果 video_type = self._infer_video_type(metadata) # TODO: 在后续计划中实现完整的 LLM 调用 return SegmentationResult( video_type=video_type, segments=[], actual_segment_count=0 )