Initial commit

This commit is contained in:
谭凯
2026-01-19 09:51:07 +08:00
commit 9ef82393be
174 changed files with 22285 additions and 0 deletions
+344
View File
@@ -0,0 +1,344 @@
#!/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())