#!/usr/bin/env python3 """ EPUB 双语翻译程序主入口 支持命令行参数和交互式使用 """ import argparse import asyncio import sys import os from pathlib import Path # 添加 src 目录到 Python 路径 sys.path.insert(0, str(Path(__file__).parent / "src")) from src.translator import EPUBTranslator from src.utils import load_config, setup_logging from rich.console import Console from rich.panel import Panel from rich.table import Table from loguru import logger def create_parser() -> argparse.ArgumentParser: """创建命令行参数解析器""" parser = argparse.ArgumentParser( description='EPUB 双语翻译程序', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" 使用示例: # 测试翻译 python main.py book.epub --test # 完整翻译 python main.py book.epub --output ./output # 使用自定义配置 python main.py book.epub --config custom_config.json # 估算翻译成本 python main.py book.epub --estimate # 禁用缓存 python main.py book.epub --no-cache """ ) parser.add_argument( 'epub_file', help='输入的 EPUB 文件路径' ) parser.add_argument( '--test', action='store_true', help='测试模式:翻译序言和一个段落进行测试' ) parser.add_argument( '--config', default='config/config.json', help='配置文件路径 (默认: config/config.json)' ) parser.add_argument( '--output', help='输出目录 (默认: 配置文件中的设置)' ) parser.add_argument( '--estimate', action='store_true', help='估算翻译成本和时间' ) parser.add_argument( '--no-cache', action='store_true', help='禁用翻译缓存' ) parser.add_argument( '--clear-cache', type=int, metavar='DAYS', help='清理指定天数前的缓存文件' ) parser.add_argument( '--cache-stats', action='store_true', help='显示缓存统计信息' ) parser.add_argument( '--verbose', '-v', action='store_true', help='详细输出模式' ) parser.add_argument( '--version', action='version', version='EPUB Translator 0.1.0' ) return parser def validate_args(args) -> None: """验证命令行参数""" # 检查 EPUB 文件是否存在 if hasattr(args, 'epub_file') and args.epub_file: epub_path = Path(args.epub_file) if not epub_path.exists(): raise FileNotFoundError(f"EPUB 文件不存在: {args.epub_file}") if not epub_path.suffix.lower() == '.epub': raise ValueError(f"文件不是 EPUB 格式: {args.epub_file}") # 检查配置文件是否存在 config_path = Path(args.config) if not config_path.exists(): raise FileNotFoundError(f"配置文件不存在: {args.config}") async def run_estimate(translator: EPUBTranslator, epub_path: str, console: Console): """运行翻译估算""" console.print("[yellow]正在估算翻译成本...[/yellow]") try: estimate = await translator.get_translation_estimate(epub_path) if not estimate: console.print("[red]估算失败[/red]") return # 显示估算结果 table = Table(title="翻译估算") table.add_column("项目", style="cyan") table.add_column("值", style="white") table.add_row("总段落数", str(estimate['total_paragraphs'])) table.add_row("章节数", str(estimate['chapters'])) table.add_row("文本长度", f"{estimate['text_length']:,} 字符") table.add_row("估算 Tokens", f"{estimate['estimated_tokens']:,}") table.add_row("估算翻译块数", str(estimate['estimated_chunks'])) table.add_row("块大小设置", f"{estimate['chunk_size']:,} 字符") table.add_row("估算时间", f"{estimate['estimated_time_minutes']:.1f} 分钟") console.print(table) # 成本估算(需要根据实际 API 定价调整) console.print("\n[yellow]注意: 实际成本取决于所选模型的定价[/yellow]") except Exception as e: console.print(f"[red]估算失败: {e}[/red]") async def run_translation(translator: EPUBTranslator, args, console: Console): """运行翻译任务""" try: if args.test: console.print("[blue]运行测试模式...[/blue]") result = await translator.translate_epub( args.epub_file, test_mode=True ) if isinstance(result, dict) and result.get('status') == 'success': console.print("[green]测试完成![/green]") else: console.print("[red]测试失败[/red]") else: console.print("[blue]开始完整翻译...[/blue]") # 确认操作 if not args.output: console.print("[yellow]将使用默认输出目录[/yellow]") output_file = await translator.translate_epub( args.epub_file, test_mode=False, output_dir=args.output ) console.print(Panel( f"翻译完成!\n输出文件: {output_file}", title="成功", border_style="green" )) except KeyboardInterrupt: console.print("\n[yellow]用户中断翻译[/yellow]") sys.exit(1) except Exception as e: console.print(f"[red]翻译失败: {e}[/red]") logger.error(f"翻译失败: {e}") sys.exit(1) def handle_cache_operations(args, config, console: Console): """处理缓存相关操作""" from src.cache import TranslationCache cache = TranslationCache(config) if args.clear_cache is not None: console.print(f"[yellow]清理 {args.clear_cache} 天前的缓存...[/yellow]") cleared = cache.clear_cache(args.clear_cache) console.print(f"[green]已清理 {cleared} 个缓存文件[/green]") return True if args.cache_stats: console.print("[cyan]缓存统计信息:[/cyan]") stats = cache.get_cache_stats() if stats.get('enabled'): table = Table() table.add_column("项目", style="cyan") table.add_column("值", style="white") table.add_row("缓存状态", "启用") table.add_row("缓存目录", stats.get('cache_directory', '')) table.add_row("文件总数", str(stats.get('total_files', 0))) table.add_row("总大小", f"{stats.get('total_size_mb', 0)} MB") table.add_row("最大保存天数", f"{stats.get('max_age_days', 0)} 天") console.print(table) # 显示按日期分布 date_dist = stats.get('date_distribution', {}) if date_dist: console.print("\n[cyan]按日期分布:[/cyan]") for date, count in sorted(date_dist.items()): console.print(f" {date}: {count} 个文件") else: console.print("[yellow]缓存未启用[/yellow]") return True return False def check_environment(): """检查运行环境""" # 检查 Python 版本 if sys.version_info < (3, 9): print("错误: 需要 Python 3.9 或更高版本") sys.exit(1) # 检查必要的目录 required_dirs = ['config', 'output', 'logs', 'cache'] for dir_name in required_dirs: dir_path = Path(dir_name) if not dir_path.exists(): dir_path.mkdir(parents=True, exist_ok=True) def display_welcome(console: Console): """显示欢迎信息""" welcome_text = """ [bold blue]EPUB 双语翻译程序 v0.1.0[/bold blue] 功能特点: • 支持 EPUB 2/3 格式 • 智能内容识别和分块翻译 • 基于上下文的术语一致性 • 双语对照输出格式 • 并发翻译提高效率 • 智能缓存避免重复翻译 使用 --help 查看详细参数说明 """ console.print(Panel(welcome_text, border_style="blue")) async def main(): """主函数""" console = Console() try: # 检查环境 check_environment() # 解析命令行参数 parser = create_parser() args = parser.parse_args() # 如果没有参数,显示帮助 if len(sys.argv) == 1: display_welcome(console) parser.print_help() return # 加载配置 try: config = load_config(args.config) except Exception as e: console.print(f"[red]加载配置失败: {e}[/red]") sys.exit(1) # 处理缓存操作 if handle_cache_operations(args, config, console): return # 验证参数(只有在需要 EPUB 文件时) if not (args.clear_cache is not None or args.cache_stats): validate_args(args) # 设置日志 if args.verbose: config['logging']['level'] = 'DEBUG' setup_logging(config) logger.info("程序启动") # 初始化翻译器 use_cache = not args.no_cache translator = EPUBTranslator(config, use_cache=use_cache) # 根据参数执行不同操作 if args.estimate: await run_estimate(translator, args.epub_file, console) else: await run_translation(translator, args, console) except KeyboardInterrupt: console.print("\n[yellow]程序被用户中断[/yellow]") sys.exit(1) except Exception as e: console.print(f"[red]程序执行失败: {e}[/red]") logger.error(f"程序执行失败: {e}") sys.exit(1) if __name__ == "__main__": # 设置事件循环策略(Windows 兼容性) if sys.platform.startswith('win'): asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) asyncio.run(main())