140 lines
4.6 KiB
Python
140 lines
4.6 KiB
Python
"""
|
|
Read Telugu text from cropped article images using Claude Vision API.
|
|
Run after a job completes to get accurate text extraction.
|
|
|
|
Usage:
|
|
python claude_ocr.py # processes latest job
|
|
python claude_ocr.py 20260526_002006 # processes specific job
|
|
"""
|
|
import os
|
|
import sys
|
|
import json
|
|
import base64
|
|
import anthropic
|
|
from pathlib import Path
|
|
|
|
OUTPUT_DIR = Path(__file__).parent / "output"
|
|
|
|
|
|
def read_article_image(client, img_path, model="claude-sonnet-4-6"):
|
|
"""Send article image to Claude and get Telugu text back."""
|
|
from PIL import Image
|
|
import io
|
|
|
|
with Image.open(img_path) as img:
|
|
# Resize if too large to fit in 10MB limit (usually > 4000x4000)
|
|
max_dim = 2800
|
|
if max(img.width, img.height) > max_dim:
|
|
ratio = max_dim / max(img.width, img.height)
|
|
new_w = int(img.width * ratio)
|
|
new_h = int(img.height * ratio)
|
|
print(f" [Claude OCR] Resizing {img.width}x{img.height} to {new_w}x{new_h} to fit API limits")
|
|
img = img.resize((new_w, new_h), Image.LANCZOS)
|
|
|
|
# Save to bytes
|
|
buffer = io.BytesIO()
|
|
img.save(buffer, format="PNG", optimize=True)
|
|
img_data = base64.standard_b64encode(buffer.getvalue()).decode("utf-8")
|
|
|
|
response = client.messages.create(
|
|
model=model,
|
|
max_tokens=4096,
|
|
messages=[{
|
|
"role": "user",
|
|
"content": [
|
|
{
|
|
"type": "image",
|
|
"source": {
|
|
"type": "base64",
|
|
"media_type": "image/png",
|
|
"data": img_data,
|
|
},
|
|
},
|
|
{
|
|
"type": "text",
|
|
"text": (
|
|
"Read all Telugu text from this newspaper article image. "
|
|
"Output ONLY the Telugu text in correct reading order "
|
|
"(top to bottom, reading each column fully before moving to the next). "
|
|
"Include headlines first, then subheadlines, then body text. "
|
|
"Do not translate. Do not add any explanation. Just the Telugu text."
|
|
),
|
|
},
|
|
],
|
|
}],
|
|
)
|
|
return response.content[0].text
|
|
|
|
|
|
def process_job(job_id=None):
|
|
api_key = os.environ.get("ANTHROPIC_API_KEY")
|
|
if not api_key:
|
|
print("Error: Set ANTHROPIC_API_KEY environment variable")
|
|
sys.exit(1)
|
|
|
|
# Find job directory
|
|
if job_id:
|
|
job_dir = OUTPUT_DIR / job_id
|
|
else:
|
|
# Use latest job
|
|
jobs = sorted(OUTPUT_DIR.iterdir(), reverse=True)
|
|
if not jobs:
|
|
print("No jobs found")
|
|
sys.exit(1)
|
|
job_dir = jobs[0]
|
|
job_id = job_dir.name
|
|
|
|
articles_dir = job_dir / "articles"
|
|
if not articles_dir.exists():
|
|
print(f"No articles found in {job_dir}")
|
|
sys.exit(1)
|
|
|
|
client = anthropic.Anthropic(api_key=api_key)
|
|
article_dirs = sorted(articles_dir.iterdir())
|
|
|
|
print(f"Job: {job_id}")
|
|
print(f"Articles: {len(article_dirs)}")
|
|
print("-" * 50)
|
|
|
|
all_text = []
|
|
|
|
for art_dir in article_dirs:
|
|
img_path = art_dir / "article.png"
|
|
if not img_path.exists():
|
|
continue
|
|
|
|
name = art_dir.name
|
|
print(f"Reading {name}...", end=" ", flush=True)
|
|
|
|
try:
|
|
text = read_article_image(client, img_path)
|
|
# Save to article.txt (overwrites old OCR output)
|
|
(art_dir / "article.txt").write_text(text, encoding="utf-8")
|
|
print(f"OK ({len(text)} chars)")
|
|
|
|
all_text.append(f"--- {name} ---\n{text}\n")
|
|
|
|
# Update info.json headline preview
|
|
info_path = art_dir / "info.json"
|
|
if info_path.exists():
|
|
info = json.loads(info_path.read_text())
|
|
first_line = next((ln.strip() for ln in text.splitlines() if ln.strip()), "")
|
|
info["headline_preview"] = first_line[:120]
|
|
info["ocr_method"] = "claude_vision"
|
|
info_path.write_text(json.dumps(info, indent=2, ensure_ascii=False))
|
|
|
|
except Exception as e:
|
|
print(f"FAILED: {e}")
|
|
all_text.append(f"--- {name} ---\n[Error: {e}]\n")
|
|
|
|
# Save combined file
|
|
combined_path = job_dir / "all_articles_claude.txt"
|
|
combined_path.write_text("\n".join(all_text), encoding="utf-8")
|
|
print("-" * 50)
|
|
print(f"Done! Combined text saved to: {combined_path}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
job_id = sys.argv[1] if len(sys.argv) > 1 else None
|
|
process_job(job_id)
|