Files
epub_bilingual_translator/scripts/pdf_qa.py
T

68 lines
5.1 KiB
Python

"""Compare cached source PDF pages and actual EPUB source anchors locally."""
import argparse
import hashlib
import json
import shutil
import sys
from pathlib import Path
from urllib.parse import quote
sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
def main():
parser=argparse.ArgumentParser(description=__doc__)
parser.add_argument('epub',type=Path);parser.add_argument('--work',type=Path,required=True)
parser.add_argument('--pages',default='1,4,16');parser.add_argument('--out',type=Path,required=True)
args=parser.parse_args()
from src.qa.headless import archive_server,book_inventory,LAYOUT_JS
from src.qa.epub_audit import audit_epub
from playwright.sync_api import sync_playwright
data=json.loads((args.work/'book.json').read_text())
pages={p['number']:p for p in data['pages']};selected=[int(p) for p in args.pages.split(',')]
if any(p not in pages for p in selected):parser.error('Requested page not present in the conversion IR')
args.out.mkdir(parents=True,exist_ok=True)
inventory=book_inventory(args.epub);evidence=[];clicks=[];sections=[]
with archive_server({'book':args.epub}) as base,sync_playwright() as pw:
browser=pw.chromium.launch(headless=True)
try:
context=browser.new_context(java_script_enabled=False,service_workers='block')
context.route('**/*',lambda route:route.continue_() if route.request.url.startswith(base+'/') else route.abort())
tab=context.new_page()
for number in selected:
anchor=f'pdf-page-{number}'
document=next((name for name,soup in inventory['documents'].items() if soup.find(id=anchor)),None)
if not document:raise ValueError(f'EPUB lost source anchor {anchor}')
source_name=f'pdf-{number:04d}.jpg';shutil.copy2(pages[number]['render'],args.out/source_name)
rendered=[]
for width in [900,390]:
tab.set_viewport_size({'width':width,'height':1100})
tab.goto(base+'/book/'+quote(document)+'#'+anchor,wait_until='networkidle')
tab.evaluate('() => document.fonts.ready')
tab.evaluate('(id) => {const e=document.getElementById(id); scrollTo(0,Math.max(0,e.getBoundingClientRect().top+scrollY-140));}',anchor)
name=f'epub-{number:04d}-{width}.png';tab.screenshot(path=str(args.out/name));rendered.append(name)
evidence.append({'source_page':number,'document':document,'viewport_width':width,'image':name,'layout_findings':tab.evaluate(LAYOUT_JS)})
sections.append(f'<section><h2>PDF 原页序 {number}</h2><div class="pair"><img src="{source_name}" alt="原扫描页"/><img src="{rendered[0]}" alt="EPUB实际排版"/><img src="{rendered[1]}" alt="窄屏排版"/></div></section>')
# Real browser navigation for every generated TOC target, including fragments.
for entry in inventory['toc']:
target=entry['target'];name,_,fragment=target.partition('#')
nav=entry['file']
nav_soup=inventory['documents'].get(nav)
if nav_soup is not None:
from src.qa.headless import destination
anchors=nav_soup.find_all('a',href=True)
link_index=next(i for i,a in enumerate(anchors) if destination(nav,a['href'])==target)
tab.goto(base+'/book/'+quote(nav),wait_until='domcontentloaded')
tab.locator('a[href]').nth(link_index).click(timeout=5000)
else:
tab.goto(base+'/book/'+quote(name)+(('#'+quote(fragment)) if fragment else ''),wait_until='domcontentloaded')
reached=(tab.url.split('#')[0]==base+'/book/'+quote(name)) and (not fragment or tab.evaluate('(id) => !!document.getElementById(id)',fragment))
clicks.append({'target':target,'destination_reached':reached})
finally:browser.close()
report={'source_sha256':data['source_sha256'],'epub_sha256':hashlib.sha256(args.epub.read_bytes()).hexdigest(),'epub_audit':audit_epub(args.epub),'evidence':evidence,'toc_navigation_checks':clicks,'visual_review':'pending','ocr_accuracy_review':'pending','limits':'Chromium continuous XHTML, not all reader pagination; source anchor may be inside a continued paragraph.'}
(args.out/'report.json').write_text(json.dumps(report,ensure_ascii=False,indent=2)+'\n')
(args.out/'review.html').write_text('<!doctype html><meta charset="utf-8"><title>PDF → EPUB QA</title><style>body{font:16px sans-serif;margin:24px}.pair{display:grid;grid-template-columns:1fr 1fr .5fr;gap:16px}img{width:100%;height:auto;align-self:start}section{margin-bottom:40px}</style><h1>PDF → EPUB 对照</h1><p>左:原扫描;中:EPUB;右:窄屏。截图生成不等于视觉或OCR验收。</p>'+''.join(sections))
print(f'PDF source comparisons: {len(selected)}; TOC target navigation: {len(clicks)}')
return int(bool(report['epub_audit']['issues']) or any(not c['destination_reached'] for c in clicks) or any(e['layout_findings'] for e in evidence))
if __name__=='__main__':sys.exit(main())