Initial commit
This commit is contained in:
@@ -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())
|
||||
Reference in New Issue
Block a user