Spaces:
Sleeping
Sleeping
| """交互式章节编辑器""" | |
| from rich.console import Console | |
| from rich.table import Table | |
| from chapterbar.chapter_extractor import BASE_COLOR, Chapter | |
| from chapterbar.chapter_validator import ChapterValidator | |
| console = Console() | |
| def format_time(seconds: float) -> str: | |
| """格式化时间为 mm:ss""" | |
| minutes = int(seconds // 60) | |
| secs = int(seconds % 60) | |
| return f"{minutes:02d}:{secs:02d}" | |
| def parse_time_input(time_str: str) -> float | None: | |
| """解析时间输入(支持 mm:ss 或秒数)""" | |
| time_str = time_str.strip() | |
| if not time_str: | |
| return None | |
| try: | |
| # 尝试解析为秒数 | |
| return float(time_str) | |
| except ValueError: | |
| pass | |
| # 尝试解析为 mm:ss 格式 | |
| if ":" in time_str: | |
| try: | |
| parts = time_str.split(":") | |
| if len(parts) == 2: | |
| minutes = int(parts[0]) | |
| seconds = int(parts[1]) | |
| return minutes * 60 + seconds | |
| except ValueError: | |
| pass | |
| return None | |
| def display_chapters_table(chapters: list[Chapter], title: str = "章节列表"): | |
| """显示章节列表""" | |
| table = Table(title=title) | |
| table.add_column("序号", style="cyan") | |
| table.add_column("开始时间", style="magenta") | |
| table.add_column("结束时间", style="magenta") | |
| table.add_column("标题", style="green") | |
| for i, chapter in enumerate(chapters, 1): | |
| table.add_row(str(i), format_time(chapter.start_time), format_time(chapter.end_time), chapter.title) | |
| console.print(table) | |
| def edit_chapter(chapters: list[Chapter], index: int, duration: float) -> bool: | |
| """编辑单个章节""" | |
| if index < 0 or index >= len(chapters): | |
| console.print("[red]✗ 无效的章节序号[/red]") | |
| return False | |
| chapter = chapters[index] | |
| console.print(f"\n[cyan]编辑章节 {index + 1}:[/cyan]") | |
| console.print(f"当前: {format_time(chapter.start_time)} - {format_time(chapter.end_time)} | {chapter.title}") | |
| console.print() | |
| # 编辑开始时间 | |
| start_input = input(f"开始时间 (mm:ss 或秒数,留空保持 {format_time(chapter.start_time)}): ").strip() | |
| if start_input: | |
| new_start = parse_time_input(start_input) | |
| if new_start is None: | |
| console.print("[red]✗ 无效的时间格式[/red]") | |
| return False | |
| chapter.start_time = new_start | |
| # 编辑结束时间 | |
| end_input = input(f"结束时间 (mm:ss 或秒数,留空保持 {format_time(chapter.end_time)}): ").strip() | |
| if end_input: | |
| new_end = parse_time_input(end_input) | |
| if new_end is None: | |
| console.print("[red]✗ 无效的时间格式[/red]") | |
| return False | |
| chapter.end_time = new_end | |
| # 编辑标题 | |
| title_input = input(f"标题 (留空保持 '{chapter.title}'): ").strip() | |
| if title_input: | |
| chapter.title = title_input | |
| console.print(f"[green]✓ 章节 {index + 1} 已更新[/green]\n") | |
| return True | |
| def add_chapter(chapters: list[Chapter], duration: float) -> bool: | |
| """添加新章节""" | |
| console.print("\n[cyan]添加新章节:[/cyan]") | |
| # 输入开始时间 | |
| start_input = input("开始时间 (mm:ss 或秒数): ").strip() | |
| start_time = parse_time_input(start_input) | |
| if start_time is None: | |
| console.print("[red]✗ 无效的时间格式[/red]") | |
| return False | |
| # 输入结束时间 | |
| end_input = input("结束时间 (mm:ss 或秒数): ").strip() | |
| end_time = parse_time_input(end_input) | |
| if end_time is None: | |
| console.print("[red]✗ 无效的时间格式[/red]") | |
| return False | |
| # 输入标题 | |
| title = input("标题: ").strip() | |
| if not title: | |
| console.print("[red]✗ 标题不能为空[/red]") | |
| return False | |
| # 创建新章节 | |
| new_chapter = Chapter(title=title, start_time=start_time, end_time=end_time, color=BASE_COLOR) | |
| # 插入到合适的位置(按开始时间排序) | |
| insert_pos = len(chapters) | |
| for i, ch in enumerate(chapters): | |
| if new_chapter.start_time < ch.start_time: | |
| insert_pos = i | |
| break | |
| chapters.insert(insert_pos, new_chapter) | |
| console.print(f"[green]✓ 章节已添加到位置 {insert_pos + 1}[/green]\n") | |
| return True | |
| def delete_chapter(chapters: list[Chapter], index: int) -> bool: | |
| """删除章节""" | |
| if index < 0 or index >= len(chapters): | |
| console.print("[red]✗ 无效的章节序号[/red]") | |
| return False | |
| removed = chapters.pop(index) | |
| console.print(f"[green]✓ 已删除章节 {index + 1}: {removed.title}[/green]\n") | |
| return True | |
| def interactive_edit_chapters(chapters: list[Chapter], duration: float) -> list[Chapter] | None: | |
| """交互式编辑章节 | |
| 返回: | |
| 编辑后的章节列表,如果用户取消则返回 None | |
| """ | |
| # 创建副本,避免修改原始数据 | |
| chapters = [Chapter(ch.title, ch.start_time, ch.end_time, ch.color) for ch in chapters] | |
| console.print("\n[bold cyan]📝 编辑模式[/bold cyan]") | |
| console.print("\n可用命令:") | |
| console.print(" [数字] - 编辑章节 (如: 1)") | |
| console.print(" [d数字] - 删除章节 (如: d2)") | |
| console.print(" [a] - 添加章节") | |
| console.print(" [l] - 显示章节列表") | |
| console.print(" [done] - 完成编辑并继续") | |
| console.print(" [cancel] - 取消编辑\n") | |
| while True: | |
| cmd = input("> ").strip().lower() | |
| if cmd == "done": | |
| # 验证章节 | |
| console.print("\n[cyan]正在验证章节...[/cyan]") | |
| errors = ChapterValidator.validate_chapters(chapters, duration) | |
| if errors: | |
| console.print("[red]✗ 验证失败:[/red]") | |
| for error in errors: | |
| console.print(f"[red] - {error.message}[/red]") | |
| console.print("\n[yellow]请修正错误后再试,或输入 'cancel' 取消编辑[/yellow]\n") | |
| continue | |
| console.print("[green]✓ 验证通过[/green]\n") | |
| return chapters | |
| elif cmd == "cancel": | |
| console.print("[yellow]已取消编辑[/yellow]\n") | |
| return None | |
| elif cmd == "l": | |
| display_chapters_table(chapters) | |
| console.print() | |
| elif cmd == "a": | |
| if add_chapter(chapters, duration): | |
| display_chapters_table(chapters) | |
| console.print() | |
| elif cmd.startswith("d") and len(cmd) > 1: | |
| try: | |
| index = int(cmd[1:]) - 1 | |
| if delete_chapter(chapters, index): | |
| display_chapters_table(chapters) | |
| console.print() | |
| except ValueError: | |
| console.print("[red]✗ 无效的命令格式,使用 'd数字' 删除章节 (如: d2)[/red]\n") | |
| elif cmd.isdigit(): | |
| index = int(cmd) - 1 | |
| if edit_chapter(chapters, index, duration): | |
| display_chapters_table(chapters) | |
| console.print() | |
| elif cmd: | |
| console.print("[red]✗ 无效的命令,输入 'l' 查看帮助[/red]\n") | |
| def confirm_chapters(chapters: list[Chapter], skip_confirm: bool = False) -> list[Chapter] | None: | |
| """确认章节配置 | |
| 参数: | |
| chapters: 章节列表 | |
| skip_confirm: 是否跳过确认(--yes 参数) | |
| 返回: | |
| 确认或编辑后的章节列表,如果用户退出则返回 None | |
| """ | |
| if skip_confirm: | |
| return chapters | |
| console.print("\n[bold]请选择操作:[/bold]") | |
| console.print(" [y] 确认并生成视频") | |
| console.print(" [e] 编辑章节") | |
| console.print(" [q] 退出\n") | |
| while True: | |
| choice = input("> ").strip().lower() | |
| if choice == "y": | |
| console.print("[green]✓ 已确认,开始生成视频...[/green]\n") | |
| return chapters | |
| elif choice == "e": | |
| # 获取视频时长(从最后一个章节) | |
| duration = chapters[-1].end_time if chapters else 0 | |
| edited_chapters = interactive_edit_chapters(chapters, duration) | |
| if edited_chapters is None: | |
| # 用户取消编辑,回到确认界面 | |
| console.print("\n[bold]请选择操作:[/bold]") | |
| console.print(" [y] 确认并生成视频") | |
| console.print(" [e] 编辑章节") | |
| console.print(" [q] 退出\n") | |
| continue | |
| return edited_chapters | |
| elif choice == "q": | |
| console.print("[yellow]已退出[/yellow]") | |
| return None | |
| else: | |
| console.print("[red]✗ 无效的选择,请输入 y/e/q[/red]\n") | |