import argparse import sys import os 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.assembly.backfiller import BackfillEngine from src.assembly.builder import BilingualBuilder from src.translation.manifest_manager import ManifestManager from src.translation.llm_client import LLMClient from src.common.data_model import BookStructure from src.common.exceptions import EpubTranslatorError from src.common.utils import setup_logger, ensure_directory from src.common.paths import get_work_dirs from src.common.config import load_global_config from dotenv import load_dotenv logger = setup_logger("pipeline_assemble") async def run_assemble(args): input_path = Path(args.input_epub) if not input_path.exists(): logger.error(f"Input file not found: {input_path}") sys.exit(1) work = get_work_dirs(input_path) work_root = work["root"] structure_path = work["structure"] manifest_path = work["manifest"] if not structure_path.exists() or not manifest_path.exists(): logger.error("Missing structure or manifest. Run Step 1.") sys.exit(1) output_dir = Path(args.output_dir) ensure_directory(output_dir) # Load Config for LLM (Optional for repair) load_dotenv() config = load_global_config() llm_conf = config.get("llm", {}) api_key = llm_conf.get("api_key") or os.getenv("OPENAI_API_KEY") # Ensure env priority llm_client = None if api_key: logger.info("Initializing LLM Client for placeholder repair...") llm_client = LLMClient( api_key=api_key, base_url=llm_conf.get("base_url"), model=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"] ) else: logger.warning("No API Key found. Placeholder repair will be disabled.") try: # Load Data structure = BookStructure.load(structure_path) manager = ManifestManager(manifest_path) manager.load() # Backfill logger.info(f"Backfilling translations (Mode: {args.mode})...") backfiller = BackfillEngine(llm_client=llm_client) updated_structure = await backfiller.backfill(structure, manager.entries, mode=args.mode) if llm_client: await llm_client.close() # Build logger.info("Building EPUB...") builder = BilingualBuilder(work_root, original_epub_path=input_path) if args.mode == "bilingual": output_filename = f"bilingual_{input_path.stem}.epub" else: output_filename = f"translated_{input_path.stem}.epub" output_path = output_dir / output_filename builder.build(updated_structure, output_path) logger.info(f"Assembly complete. Output: {output_path}") except EpubTranslatorError as e: logger.error(f"Assembly failed: {e}") sys.exit(1) except Exception as e: logger.critical(f"Unexpected error: {e}") import traceback traceback.print_exc() sys.exit(1) if __name__ == "__main__": parser = argparse.ArgumentParser(description="Step 3: Assembly (Backfill + Build)") parser.add_argument("input_epub", help="Path to original EPUB") parser.add_argument("--output-dir", default="output", help="Output directory") parser.add_argument("--bilingual", action="store_true", help="Output bilingual version (default target only)") args = parser.parse_args() args.mode = "bilingual" if args.bilingual else "target_only" import asyncio asyncio.run(run_assemble(args))