Improve EPUB restoration, book terminology and Calibre QA workflow

This commit is contained in:
Kai Tan
2026-10-08 07:25:27 +08:00
parent 40491e6386
commit caf9d8f4bb
35 changed files with 1439 additions and 959 deletions
+5
View File
@@ -1,4 +1,5 @@
import argparse
import hashlib
import sys
import os
from pathlib import Path
@@ -33,11 +34,15 @@ def run_preprocess(args):
if structure_path.exists() and not args.force:
logger.info(f"Reusing existing structure: {structure_path}")
structure = BookStructure.load(structure_path)
if structure.source_sha256 != hashlib.sha256(input_path.read_bytes()).hexdigest():
structure = BookStructure.load(EpubCleaner(input_path, work["root"]).clean())
else:
logger.info("Cleaning EPUB...")
cleaner = EpubCleaner(input_path, work["root"])
structure_path = cleaner.clean()
structure = BookStructure.load(structure_path)
if structure.source_sha256 != hashlib.sha256(input_path.read_bytes()).hexdigest():
structure = BookStructure.load(EpubCleaner(input_path, work["root"]).clean())
# 2. Extract Text
logger.info("Extracting text segments...")
+21 -3
View File
@@ -7,6 +7,7 @@ from pathlib import Path
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from src.translation.translator_engine import Translator
from src.translation.glossary import prepare_glossary
from src.translation.llm_client import LLMClient
from src.translation.manifest_manager import ManifestManager
from src.preprocessing.profiler import BookProfiler
@@ -60,6 +61,7 @@ async def run_translate(args):
logger.error("OPENAI_API_KEY not found in env or config.")
sys.exit(1)
llm = None
try:
# Load Manifest
manager = ManifestManager(manifest_path)
@@ -74,23 +76,34 @@ async def run_translate(args):
requests_per_minute=llm_conf.get("requests_per_minute", 60),
concurrent_requests=llm_conf.get("concurrent_requests", 5),
timeout=llm_conf.get("timeout", 180),
max_output_tokens=llm_conf.get("max_output_tokens", 16000),
chunk_dir=work["chunks"]
)
# Profile
profiler = BookProfiler(llm)
profiler = BookProfiler(llm, cache_path=work["root"] / "profile.json")
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)
glossary = await prepare_glossary(
manager.entries, llm, work["root"] / "glossary.json",
override=getattr(args, 'glossary', None) or trans_conf.get("glossary_path"),
auto=trans_conf.get("auto_glossary", True),
chunk_chars=trans_conf.get("glossary_scan_chars", 24000))
translator = Translator(llm, chunk_size=target_chunk_size, max_concurrent=concurrent_reqs,
glossary=glossary, validation_retries=trans_conf.get("validation_retries", 2),
on_chunk_complete=manager.save)
await translator.translate(manager.entries, profile)
# Save final state
manager.save()
await llm.close()
if any(not e.translated_text or e.validation_errors for e in manager.entries):
raise ValueError("Translation incomplete; inspect manifest validation_errors and resume")
logger.info("Translation complete.")
@@ -101,10 +114,15 @@ async def run_translate(args):
logger.critical(f"Unexpected error: {e}")
sys.exit(1)
finally:
if llm:
await llm.close()
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("--glossary", help="Per-book JSON terminology file")
parser.add_argument("--model", default=None, help="LLM Model (overrides config)")
args = parser.parse_args()
+6 -1
View File
@@ -7,6 +7,7 @@ from pathlib import Path
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from src.assembly.backfiller import BackfillEngine
from src.assembly.restoration_engine import RestorationEngine
from src.assembly.builder import BilingualBuilder
from src.translation.manifest_manager import ManifestManager
from src.translation.llm_client import LLMClient
@@ -65,7 +66,11 @@ async def run_assemble(args):
# Backfill
logger.info(f"Backfilling translations (Mode: {args.mode})...")
backfiller = BackfillEngine(llm_client=llm_client)
await RestorationEngine(llm_client).restore_entries(manager.entries)
manager.save()
if any(e.translated_text and not e.translated_html for e in manager.entries):
raise ValueError("Format validation failed; inspect manifest")
backfiller = BackfillEngine()
updated_structure = await backfiller.backfill(structure, manager.entries, mode=args.mode)
if llm_client:
+2
View File
@@ -44,6 +44,8 @@ async def run_build(args):
manager = ManifestManager(manifest_path)
manager.load()
if any(e.translated_text and not e.translated_html for e in manager.entries):
raise ValueError("Run format restoration before building; missing validated HTML")
# Backfill (Pure Injection)
logger.info(f"Injecting content (Mode: {args.mode})...")
backfiller = BackfillEngine() # No dependencies needed