import argparse import sys import os import asyncio from pathlib import Path # Add project root to sys.path sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) from src.translation.translator_engine import Translator from src.translation.llm_client import LLMClient from src.translation.manifest_manager import ManifestManager from src.preprocessing.profiler import BookProfiler from src.common.utils import setup_logger from src.common.exceptions import EpubTranslatorError from src.common.config import load_global_config from src.common.paths import get_work_dirs logger = setup_logger("pipeline_translate") async def run_translate(args): # Resolve paths input_path = None if args.input_epub: input_path = Path(args.input_epub) elif args.book_name: # Try to infer from work dir if book_name provided (legacy support) # But paths.py needs input_path to determine work dir. # So we really need input_epub for get_work_dirs. # But if the user only provides book_name, we might be in trouble with get_work_dirs logic # which relies on input_path.stem. # Let's check get_work_dirs again. pass # Actually get_work_dirs relies on input_path.stem. # If the user gives only --book-name, we can't easily construct input_path # unless we fake it or change get_work_dirs. # However, pipeline instructions say input_epub is required for 02_translate. # checking args... 02_translate has --input-epub AND --book-name. if args.input_epub: input_path = Path(args.input_epub) else: logger.error("Must provide --input-epub") sys.exit(1) work = get_work_dirs(input_path) manifest_path = work["manifest"] if not manifest_path.exists(): logger.error(f"Manifest not found: {manifest_path}. Run Step 1 first.") sys.exit(1) # Load Config config = load_global_config() llm_conf = config.get("llm", {}) trans_conf = config.get("translation", {}) api_key = llm_conf.get("api_key") if not api_key: logger.error("OPENAI_API_KEY not found in env or config.") sys.exit(1) try: # Load Manifest manager = ManifestManager(manifest_path) manager.load() # Init components # Allow CLI args to override config if needed (not implemented yet, taking config partial priority) llm = LLMClient( api_key=api_key, base_url=llm_conf.get("base_url"), model=args.model if args.model else llm_conf.get("model", "gpt-3.5-turbo"), requests_per_minute=llm_conf.get("requests_per_minute", 60), concurrent_requests=llm_conf.get("concurrent_requests", 5), chunk_dir=work["chunks"] ) # Profile profiler = BookProfiler(llm) profile = await profiler.analyze(manager.entries) logger.info(f"Book Profile: {profile.title} ({profile.genre})") # Translate target_chunk_size = trans_conf.get("chunk_size", 4000) concurrent_reqs = llm_conf.get("concurrent_requests", 5) translator = Translator(llm, chunk_size=target_chunk_size, max_concurrent=concurrent_reqs) await translator.translate(manager.entries, profile) # Save final state manager.save() await llm.close() logger.info("Translation complete.") except EpubTranslatorError as e: logger.error(f"Translation failed: {e}") sys.exit(1) except Exception as e: logger.critical(f"Unexpected error: {e}") sys.exit(1) if __name__ == "__main__": parser = argparse.ArgumentParser(description="Step 2: Translation") parser.add_argument("--input-epub", help="Path to original EPUB (to derive book name)") parser.add_argument("--book-name", help="Book name (folder name in work/)") parser.add_argument("--model", default=None, help="LLM Model (overrides config)") args = parser.parse_args() asyncio.run(run_translate(args))