Spaces:
Sleeping
Sleeping
| """章节验证器""" | |
| from dataclasses import dataclass | |
| from chapterbar.chapter_extractor import Chapter | |
| class ValidationError: | |
| """验证错误""" | |
| chapter_index: int | |
| error_type: str | |
| message: str | |
| is_warning: bool = False | |
| class ChapterValidator: | |
| """章节验证器""" | |
| def __init__(self, chapters: list[Chapter], total_duration: float): | |
| """ | |
| 初始化验证器 | |
| Args: | |
| chapters: 章节列表 | |
| total_duration: 视频总时长(秒) | |
| """ | |
| self.chapters = chapters | |
| self.total_duration = total_duration | |
| self.errors: list[ValidationError] = [] | |
| self.warnings: list[ValidationError] = [] | |
| def validate(self) -> tuple[bool, list[ValidationError], list[ValidationError]]: | |
| """ | |
| 验证章节列表 | |
| Returns: | |
| (is_valid, errors, warnings) | |
| """ | |
| self.errors = [] | |
| self.warnings = [] | |
| # 基础检查 | |
| self._check_empty() | |
| self._check_basic_fields() | |
| # 时间检查 | |
| self._check_time_order() | |
| self._check_time_overlap() | |
| self._check_time_range() | |
| self._check_time_gaps() | |
| # 内容检查 | |
| self._check_titles() | |
| self._check_duration() | |
| is_valid = len(self.errors) == 0 | |
| return is_valid, self.errors, self.warnings | |
| def _check_empty(self): | |
| """检查章节列表是否为空""" | |
| if not self.chapters: | |
| self.errors.append(ValidationError(chapter_index=-1, error_type="empty", message="章节列表为空")) | |
| def _check_basic_fields(self): | |
| """检查基本字段""" | |
| for i, chapter in enumerate(self.chapters): | |
| if not hasattr(chapter, "start_time") or chapter.start_time is None: | |
| self.errors.append( | |
| ValidationError( | |
| chapter_index=i, | |
| error_type="missing_field", | |
| message=f"章节 {i + 1} 缺少开始时间", | |
| ) | |
| ) | |
| if not hasattr(chapter, "end_time") or chapter.end_time is None: | |
| self.errors.append( | |
| ValidationError( | |
| chapter_index=i, | |
| error_type="missing_field", | |
| message=f"章节 {i + 1} 缺少结束时间", | |
| ) | |
| ) | |
| if not hasattr(chapter, "title") or not chapter.title: | |
| self.errors.append( | |
| ValidationError( | |
| chapter_index=i, | |
| error_type="missing_field", | |
| message=f"章节 {i + 1} 缺少标题", | |
| ) | |
| ) | |
| def _check_time_order(self): | |
| """检查时间顺序""" | |
| for i, chapter in enumerate(self.chapters): | |
| if chapter.start_time >= chapter.end_time: | |
| self.errors.append( | |
| ValidationError( | |
| chapter_index=i, | |
| error_type="time_order", | |
| message=(f"章节 {i + 1} 开始时间 ({chapter.start_time}s) >= 结束时间 ({chapter.end_time}s)"), | |
| ) | |
| ) | |
| def _check_time_overlap(self): | |
| """检查时间重叠""" | |
| for i in range(len(self.chapters) - 1): | |
| current = self.chapters[i] | |
| next_chapter = self.chapters[i + 1] | |
| if current.end_time > next_chapter.start_time: | |
| self.errors.append( | |
| ValidationError( | |
| chapter_index=i, | |
| error_type="time_overlap", | |
| message=( | |
| f"章节 {i + 1} 和章节 {i + 2} 时间重叠:章节 {i + 1} 结束于 " | |
| f"{current.end_time}s,但章节 {i + 2} 开始于 {next_chapter.start_time}s" | |
| ), | |
| ) | |
| ) | |
| def _check_time_range(self): | |
| """检查时间范围""" | |
| for i, chapter in enumerate(self.chapters): | |
| if chapter.start_time < 0: | |
| self.errors.append( | |
| ValidationError( | |
| chapter_index=i, | |
| error_type="time_range", | |
| message=f"章节 {i + 1} 开始时间 ({chapter.start_time}s) 不能为负数", | |
| ) | |
| ) | |
| if chapter.end_time > self.total_duration: | |
| self.errors.append( | |
| ValidationError( | |
| chapter_index=i, | |
| error_type="time_range", | |
| message=( | |
| f"章节 {i + 1} 结束时间 ({chapter.end_time}s) 超出视频总时长 ({self.total_duration}s)" | |
| ), | |
| ) | |
| ) | |
| def _check_time_gaps(self): | |
| """检查时间间隙(警告)""" | |
| # 检查第一个章节是否从 0 开始 | |
| if self.chapters and self.chapters[0].start_time > 0: | |
| self.warnings.append( | |
| ValidationError( | |
| chapter_index=0, | |
| error_type="time_gap", | |
| message=( | |
| f"第一个章节从 {self.chapters[0].start_time}s 开始,前面有 " | |
| f"{self.chapters[0].start_time}s 的间隙" | |
| ), | |
| is_warning=True, | |
| ) | |
| ) | |
| # 检查章节之间的间隙 | |
| for i in range(len(self.chapters) - 1): | |
| current = self.chapters[i] | |
| next_chapter = self.chapters[i + 1] | |
| gap = next_chapter.start_time - current.end_time | |
| if gap > 0: | |
| self.warnings.append( | |
| ValidationError( | |
| chapter_index=i, | |
| error_type="time_gap", | |
| message=f"章节 {i + 1} 和章节 {i + 2} 之间有 {gap}s 的间隙", | |
| is_warning=True, | |
| ) | |
| ) | |
| # 检查最后一个章节是否到达视频结尾 | |
| if self.chapters and self.chapters[-1].end_time < self.total_duration: | |
| gap = self.total_duration - self.chapters[-1].end_time | |
| self.warnings.append( | |
| ValidationError( | |
| chapter_index=len(self.chapters) - 1, | |
| error_type="time_gap", | |
| message=(f"最后一个章节结束于 {self.chapters[-1].end_time}s,距离视频结尾还有 {gap}s"), | |
| is_warning=True, | |
| ) | |
| ) | |
| def _check_titles(self): | |
| """检查标题""" | |
| for i, chapter in enumerate(self.chapters): | |
| if chapter.title and len(chapter.title.strip()) == 0: | |
| self.errors.append( | |
| ValidationError(chapter_index=i, error_type="empty_title", message=f"章节 {i + 1} 标题为空") | |
| ) | |
| if chapter.title and len(chapter.title) > 100: | |
| self.warnings.append( | |
| ValidationError( | |
| chapter_index=i, | |
| error_type="long_title", | |
| message=(f"章节 {i + 1} 标题过长 ({len(chapter.title)} 字符),建议不超过 100 字符"), | |
| is_warning=True, | |
| ) | |
| ) | |
| def _check_duration(self): | |
| """检查章节时长(警告)""" | |
| for i, chapter in enumerate(self.chapters): | |
| duration = chapter.end_time - chapter.start_time | |
| if duration < 5: | |
| self.warnings.append( | |
| ValidationError( | |
| chapter_index=i, | |
| error_type="short_duration", | |
| message=f"章节 {i + 1} 时长过短 ({duration}s),建议至少 5 秒", | |
| is_warning=True, | |
| ) | |
| ) | |
| if duration > 600: # 10 分钟 | |
| self.warnings.append( | |
| ValidationError( | |
| chapter_index=i, | |
| error_type="long_duration", | |
| message=f"章节 {i + 1} 时长过长 ({duration}s),建议不超过 10 分钟", | |
| is_warning=True, | |
| ) | |
| ) | |