603 lines
24 KiB
Python
603 lines
24 KiB
Python
# backend/utils.py
|
|
import os
|
|
import time
|
|
import pytesseract
|
|
from PIL import Image
|
|
from pdf2image import convert_from_path
|
|
import shutil
|
|
import asyncio
|
|
import uuid
|
|
import subprocess
|
|
import base64
|
|
import re
|
|
from models import get_prompt
|
|
|
|
# These imports were only used by TxtToPdf functionality and have been removed
|
|
|
|
UPLOAD_FOLDER = "uploads"
|
|
OUTPUT_FOLDER = "outputs"
|
|
|
|
def encode_image(file_path):
|
|
with open(file_path, "rb") as f:
|
|
return base64.b64encode(f.read()).decode("utf-8")
|
|
|
|
def run_tesseract(file_path):
|
|
"""
|
|
Si el archivo es un PDF, se realiza OCR página a página añadiendo al principio de cada una
|
|
la cabecera con el formato [Page 0001], [Page 0002], etc.
|
|
Para otros formatos se realiza OCR normal.
|
|
"""
|
|
extracted_text = ""
|
|
if file_path.lower().endswith(".pdf"):
|
|
try:
|
|
pages = convert_from_path(file_path)
|
|
for i, page in enumerate(pages, start=1):
|
|
page_text = pytesseract.image_to_string(page, lang='eng')
|
|
extracted_text += f"[Page {i:04d}]\n{page_text}\n\n"
|
|
except Exception as e:
|
|
extracted_text = f"❌ Error processing PDF: {str(e)}"
|
|
else:
|
|
image = Image.open(file_path)
|
|
extracted_text = pytesseract.image_to_string(image, lang='eng')
|
|
return extracted_text
|
|
|
|
def call_api_correction(api, model, text, prompt_key="ocr_correction"):
|
|
prompt = get_prompt(prompt_key) + text
|
|
try:
|
|
import google.generativeai as genai
|
|
except ImportError:
|
|
raise ImportError("Please install 'google-generativeai' to use Gemini.")
|
|
try:
|
|
loop = asyncio.get_event_loop()
|
|
except RuntimeError:
|
|
loop = asyncio.new_event_loop()
|
|
asyncio.set_event_loop(loop)
|
|
|
|
# Get the API key
|
|
api_key = os.environ.get("GEMINI_API_KEY")
|
|
if not api_key:
|
|
return "❌ GEMINI_API_KEY not found in environment variables"
|
|
|
|
# Configure API
|
|
genai.configure(api_key=api_key)
|
|
|
|
# Create model instance
|
|
text_model = genai.GenerativeModel(model)
|
|
|
|
# Generate content
|
|
response = text_model.generate_content(prompt)
|
|
|
|
# Get text response
|
|
return response.text
|
|
|
|
def call_api_ocr(api, model, file_path, prompt_key="ocr"):
|
|
"""
|
|
OCR processing using Gemini's vision capabilities.
|
|
Implements image optimization and retries with exponential backoff for handling timeouts.
|
|
"""
|
|
try:
|
|
import sys
|
|
import traceback
|
|
import mimetypes
|
|
import base64
|
|
import time
|
|
from PIL import Image
|
|
import google.generativeai as genai
|
|
except ImportError:
|
|
raise ImportError("Please install 'google-generativeai' to use Gemini.")
|
|
|
|
try:
|
|
loop = asyncio.get_event_loop()
|
|
except RuntimeError:
|
|
loop = asyncio.new_event_loop()
|
|
asyncio.set_event_loop(loop)
|
|
|
|
MAX_RETRIES = 3
|
|
RETRY_DELAY = 2 # Initial delay in seconds, will increase with each retry
|
|
|
|
try:
|
|
# Debug information
|
|
print(f"DEBUG OCR: File path: {file_path}")
|
|
print(f"DEBUG OCR: File exists: {os.path.exists(file_path)}")
|
|
print(f"DEBUG OCR: File size: {os.path.getsize(file_path) if os.path.exists(file_path) else 'N/A'}")
|
|
|
|
# Get the API key
|
|
api_key = os.environ.get("GEMINI_API_KEY")
|
|
if not api_key:
|
|
error_msg = "GEMINI_API_KEY not found in environment variables"
|
|
print(f"DEBUG OCR ERROR: {error_msg}")
|
|
return f"❌ {error_msg}"
|
|
|
|
# Configure API
|
|
print(f"DEBUG OCR: Configuring API with key: {api_key[:4]}...{api_key[-4:] if len(api_key) > 8 else ''}")
|
|
genai.configure(api_key=api_key)
|
|
|
|
# Open and optimize the image
|
|
try:
|
|
# Open the image
|
|
img = Image.open(file_path)
|
|
|
|
# Check image size - resize if too large (max 1600px on any side)
|
|
max_size = 1600
|
|
original_size = img.size
|
|
if img.width > max_size or img.height > max_size:
|
|
# Calculate new size preserving aspect ratio
|
|
if img.width > img.height:
|
|
new_width = max_size
|
|
new_height = int(img.height * (max_size / img.width))
|
|
else:
|
|
new_height = max_size
|
|
new_width = int(img.width * (max_size / img.height))
|
|
|
|
img = img.resize((new_width, new_height), Image.LANCZOS)
|
|
print(f"DEBUG OCR: Resized image from {original_size} to {img.size}")
|
|
|
|
# Convert to RGB if needed (e.g., for PNG with transparency)
|
|
if img.mode in ('RGBA', 'LA') or (img.mode == 'P' and 'transparency' in img.info):
|
|
background = Image.new('RGB', img.size, (255, 255, 255))
|
|
if img.mode == 'P':
|
|
img = img.convert('RGBA')
|
|
background.paste(img, (0, 0), img)
|
|
img = background
|
|
print("DEBUG OCR: Converted image to RGB")
|
|
elif img.mode != 'RGB':
|
|
img = img.convert('RGB')
|
|
print("DEBUG OCR: Converted image to RGB")
|
|
|
|
# Save to BytesIO and encode
|
|
import io
|
|
buffer = io.BytesIO()
|
|
img.save(buffer, format="JPEG", quality=85, optimize=True)
|
|
buffer.seek(0)
|
|
image_bytes = buffer.read()
|
|
file_base64 = base64.b64encode(image_bytes).decode('utf-8')
|
|
print(f"DEBUG OCR: Successfully processed and encoded image ({len(file_base64)} chars)")
|
|
except Exception as img_error:
|
|
error_msg = f"Error processing image: {str(img_error)}"
|
|
print(f"DEBUG OCR ERROR: {error_msg}")
|
|
print(traceback.format_exc())
|
|
return f"❌ {error_msg}"
|
|
|
|
# Get prompt
|
|
prompt = get_prompt(prompt_key)
|
|
print(f"DEBUG OCR: Using prompt: {prompt[:50]}...")
|
|
|
|
# Create model instance
|
|
vision_model = genai.GenerativeModel(model)
|
|
print(f"DEBUG OCR: Created model instance for model: {model}")
|
|
|
|
# Create image data in the format required by the API
|
|
image_data = {
|
|
"mime_type": "image/jpeg", # Always using JPEG for consistency
|
|
"data": file_base64
|
|
}
|
|
|
|
print(f"DEBUG OCR: Image prepared for API. Generating content with retries...")
|
|
|
|
# Initialize variables for retry logic
|
|
retry_count = 0
|
|
current_delay = RETRY_DELAY
|
|
last_error = None
|
|
|
|
# Retry loop
|
|
while retry_count < MAX_RETRIES:
|
|
try:
|
|
if retry_count > 0:
|
|
print(f"DEBUG OCR: Retry {retry_count}/{MAX_RETRIES} after {current_delay}s delay")
|
|
|
|
# Generate content with the image and the prompt
|
|
response = vision_model.generate_content([
|
|
{"inline_data": image_data},
|
|
prompt
|
|
], generation_config={
|
|
"temperature": 0.2,
|
|
"top_p": 0.95,
|
|
"top_k": 40,
|
|
"max_output_tokens": 1024, # Limit output size to avoid timeouts
|
|
})
|
|
|
|
print(f"DEBUG OCR: Response received successfully")
|
|
|
|
# Return the text response
|
|
return response.text
|
|
|
|
except Exception as api_error:
|
|
last_error = api_error
|
|
error_str = str(api_error)
|
|
print(f"DEBUG OCR ERROR on attempt {retry_count+1}: {error_str}")
|
|
|
|
# Check if this is a timeout or rate limit error (worth retrying)
|
|
if "Deadline Exceeded" in error_str or "429" in error_str or "quota" in error_str.lower() or "timeout" in error_str.lower():
|
|
retry_count += 1
|
|
if retry_count < MAX_RETRIES:
|
|
print(f"DEBUG OCR: Timeout or rate limit error, will retry in {current_delay}s")
|
|
time.sleep(current_delay)
|
|
current_delay *= 2 # Exponential backoff
|
|
else:
|
|
print("DEBUG OCR: Max retries reached")
|
|
else:
|
|
# Non-retryable error
|
|
print("DEBUG OCR: Non-retryable error, giving up")
|
|
break
|
|
|
|
# If we get here, all retries failed
|
|
error_detail = f"Error after {MAX_RETRIES} attempts: {str(last_error)}"
|
|
print(f"DEBUG OCR ERROR: {error_detail}")
|
|
return f"❌ Error in OCR: {error_detail}"
|
|
|
|
except Exception as e:
|
|
error_detail = f"Error: {str(e)}\n"
|
|
error_detail += f"Exception type: {type(e).__name__}\n"
|
|
error_detail += f"Traceback: {traceback.format_exc()}"
|
|
print(f"DEBUG OCR ERROR: {error_detail}")
|
|
|
|
return f"❌ Error in OCR: {str(e)}"
|
|
|
|
def describe_image(file_path, api, model, prompt_key="image_desc", update_progress=None, is_cancelled=None):
|
|
"""
|
|
Process an image file and generate a description using the specified AI model.
|
|
Uses direct base64 encoding for Gemini's vision capabilities.
|
|
Implements retries with exponential backoff for handling timeouts.
|
|
"""
|
|
import sys
|
|
import traceback
|
|
import mimetypes
|
|
import base64
|
|
import time
|
|
from PIL import Image
|
|
|
|
print(f"DEBUG IMAGE_DESC: Starting image description function for file: {file_path}")
|
|
print(f"DEBUG IMAGE_DESC: Using API: {api}, Model: {model}, Prompt key: {prompt_key}")
|
|
|
|
MAX_RETRIES = 3
|
|
RETRY_DELAY = 2 # Initial delay in seconds, will increase with each retry
|
|
|
|
try:
|
|
if update_progress:
|
|
update_progress(20, "⏳ Loading image file...")
|
|
|
|
if is_cancelled and is_cancelled():
|
|
return "Process cancelled."
|
|
|
|
# Check if file exists
|
|
if not os.path.exists(file_path):
|
|
error_msg = f"File not found: {file_path}"
|
|
print(f"DEBUG IMAGE_DESC: {error_msg}")
|
|
if update_progress:
|
|
update_progress(0, f"❌ {error_msg}")
|
|
return f"❌ {error_msg}"
|
|
|
|
# Get MIME type of image
|
|
mime_type = mimetypes.guess_type(file_path)[0] or 'image/png'
|
|
print(f"DEBUG IMAGE_DESC: File path: {file_path}")
|
|
print(f"DEBUG IMAGE_DESC: MIME type: {mime_type}")
|
|
|
|
# Read the image, resize if needed to reduce size, and encode it to base64
|
|
if update_progress:
|
|
update_progress(40, "🔍 Processing image...")
|
|
|
|
try:
|
|
# Open and optimize the image
|
|
img = Image.open(file_path)
|
|
|
|
# Check image size - resize if too large (max 1600px on any side)
|
|
max_size = 1600
|
|
original_size = img.size
|
|
if img.width > max_size or img.height > max_size:
|
|
# Calculate new size preserving aspect ratio
|
|
if img.width > img.height:
|
|
new_width = max_size
|
|
new_height = int(img.height * (max_size / img.width))
|
|
else:
|
|
new_height = max_size
|
|
new_width = int(img.width * (max_size / img.height))
|
|
|
|
img = img.resize((new_width, new_height), Image.LANCZOS)
|
|
print(f"DEBUG IMAGE_DESC: Resized image from {original_size} to {img.size}")
|
|
|
|
# Convert to RGB if needed (e.g., for PNG with transparency)
|
|
if img.mode in ('RGBA', 'LA') or (img.mode == 'P' and 'transparency' in img.info):
|
|
# Create a white background image
|
|
background = Image.new('RGB', img.size, (255, 255, 255))
|
|
# Paste the image on the background (handles transparency)
|
|
if img.mode == 'P':
|
|
img = img.convert('RGBA')
|
|
background.paste(img, (0, 0), img)
|
|
img = background
|
|
print("DEBUG IMAGE_DESC: Converted image to RGB")
|
|
elif img.mode != 'RGB':
|
|
img = img.convert('RGB')
|
|
print("DEBUG IMAGE_DESC: Converted image to RGB")
|
|
|
|
# Save to BytesIO and encode
|
|
import io
|
|
buffer = io.BytesIO()
|
|
img.save(buffer, format="JPEG", quality=85, optimize=True)
|
|
buffer.seek(0)
|
|
image_bytes = buffer.read()
|
|
image_base64 = base64.b64encode(image_bytes).decode('utf-8')
|
|
print(f"DEBUG IMAGE_DESC: Successfully processed and encoded image ({len(image_base64)} chars)")
|
|
except Exception as e:
|
|
error_msg = f"Error processing image file: {str(e)}"
|
|
print(f"DEBUG IMAGE_DESC ERROR: {error_msg}")
|
|
print(traceback.format_exc())
|
|
if update_progress:
|
|
update_progress(0, f"❌ {error_msg}")
|
|
return f"❌ {error_msg}"
|
|
|
|
# Import Gemini libraries - using the correct package name
|
|
try:
|
|
print("DEBUG IMAGE_DESC: Attempting to import google-generativeai library")
|
|
import google.generativeai as genai
|
|
print("DEBUG IMAGE_DESC: Successfully imported google.generativeai library")
|
|
except ImportError as ie:
|
|
error_msg = f"Failed to import Google's generativeai library: {str(ie)}. Please install it with: pip install google-generativeai"
|
|
print(f"DEBUG IMAGE_DESC ERROR: {error_msg}")
|
|
if update_progress:
|
|
update_progress(0, f"❌ {error_msg}")
|
|
return f"❌ {error_msg}"
|
|
|
|
# Configure API
|
|
api_key = os.environ.get("GEMINI_API_KEY")
|
|
if not api_key:
|
|
error_msg = "GEMINI_API_KEY not found in environment variables"
|
|
print(f"DEBUG IMAGE_DESC ERROR: {error_msg}")
|
|
if update_progress:
|
|
update_progress(0, f"❌ {error_msg}")
|
|
return f"❌ {error_msg}"
|
|
|
|
print(f"DEBUG IMAGE_DESC: Configuring API with key: {api_key[:4]}...{api_key[-4:] if len(api_key) > 8 else ''}")
|
|
genai.configure(api_key=api_key)
|
|
|
|
# Get prompt from configuration
|
|
prompt = get_prompt(prompt_key)
|
|
print(f"DEBUG IMAGE_DESC: Using prompt: {prompt[:50]}...")
|
|
|
|
if update_progress:
|
|
update_progress(60, "🤖 Generating description...")
|
|
|
|
# Generation parameters - reduce complexity for better performance
|
|
generation_config = {
|
|
"temperature": 0.2, # Lower temperature for more deterministic output
|
|
"top_p": 0.95,
|
|
"top_k": 40,
|
|
"max_output_tokens": 800, # Limit output size
|
|
}
|
|
|
|
print(f"DEBUG IMAGE_DESC: Creating model instance for model: {model}")
|
|
# Create model instance
|
|
vision_model = genai.GenerativeModel(model)
|
|
|
|
# Format image for the API
|
|
image_data = {
|
|
"mime_type": "image/jpeg", # Always using JPEG for consistency
|
|
"data": image_base64
|
|
}
|
|
|
|
print(f"DEBUG IMAGE_DESC: Image prepared for API. Generating content with retries...")
|
|
|
|
# Initialize variables for retry logic
|
|
retry_count = 0
|
|
current_delay = RETRY_DELAY
|
|
last_error = None
|
|
|
|
# Retry loop
|
|
while retry_count < MAX_RETRIES:
|
|
try:
|
|
if retry_count > 0:
|
|
print(f"DEBUG IMAGE_DESC: Retry {retry_count}/{MAX_RETRIES} after {current_delay}s delay")
|
|
if update_progress:
|
|
update_progress(60, f"🔄 Retry {retry_count}/{MAX_RETRIES}...")
|
|
|
|
# Generate content using the correct format for v0.3.1
|
|
response = vision_model.generate_content([
|
|
{"inline_data": image_data},
|
|
prompt
|
|
], generation_config=generation_config)
|
|
|
|
print(f"DEBUG IMAGE_DESC: Successfully received response from Gemini API")
|
|
|
|
if update_progress:
|
|
update_progress(100, "✅ Description generated")
|
|
|
|
# Get text response
|
|
print(f"DEBUG IMAGE_DESC: Returning text response of length: {len(response.text) if response.text else 0}")
|
|
return response.text
|
|
|
|
except Exception as api_error:
|
|
last_error = api_error
|
|
error_str = str(api_error)
|
|
print(f"DEBUG IMAGE_DESC ERROR on attempt {retry_count+1}: {error_str}")
|
|
|
|
# Check if this is a timeout or rate limit error (worth retrying)
|
|
if "Deadline Exceeded" in error_str or "429" in error_str or "quota" in error_str.lower() or "timeout" in error_str.lower():
|
|
retry_count += 1
|
|
if retry_count < MAX_RETRIES:
|
|
print(f"DEBUG IMAGE_DESC: Timeout or rate limit error, will retry in {current_delay}s")
|
|
time.sleep(current_delay)
|
|
current_delay *= 2 # Exponential backoff
|
|
else:
|
|
print("DEBUG IMAGE_DESC: Max retries reached")
|
|
else:
|
|
# Non-retryable error
|
|
print("DEBUG IMAGE_DESC: Non-retryable error, giving up")
|
|
break
|
|
|
|
# If we get here, all retries failed
|
|
error_msg = f"Error generating content after {MAX_RETRIES} attempts: {str(last_error)}"
|
|
print(f"DEBUG IMAGE_DESC ERROR: {error_msg}")
|
|
if update_progress:
|
|
update_progress(0, f"❌ {error_msg}")
|
|
return f"❌ {error_msg}"
|
|
|
|
except Exception as e:
|
|
error_msg = f"Unexpected error: {str(e)}"
|
|
print(f"DEBUG IMAGE_DESC ERROR: {error_msg}")
|
|
print(traceback.format_exc())
|
|
if update_progress:
|
|
update_progress(0, f"❌ {error_msg}")
|
|
return f"❌ {error_msg}"
|
|
|
|
def call_api_translation(api, model, text, target_language, prompt_key="translation"):
|
|
prompt_template = get_prompt(prompt_key)
|
|
prompt = prompt_template.format(target_language=target_language) + text
|
|
try:
|
|
import google.generativeai as genai
|
|
except ImportError:
|
|
raise ImportError("Please install 'google-generativeai' to use Gemini.")
|
|
try:
|
|
loop = asyncio.get_event_loop()
|
|
except RuntimeError:
|
|
loop = asyncio.new_event_loop()
|
|
asyncio.set_event_loop(loop)
|
|
|
|
# Get the API key
|
|
api_key = os.environ.get("GEMINI_API_KEY")
|
|
if not api_key:
|
|
return "❌ GEMINI_API_KEY not found in environment variables"
|
|
|
|
# Configure API
|
|
genai.configure(api_key=api_key)
|
|
|
|
# Create model instance
|
|
text_model = genai.GenerativeModel(model)
|
|
|
|
# Generate content - simpler format for v0.3.1
|
|
response = text_model.generate_content(prompt, generation_config={
|
|
"temperature": 0.2,
|
|
"top_p": 0.95,
|
|
"top_k": 40
|
|
})
|
|
|
|
# Get text response
|
|
return response.text
|
|
|
|
def embed_ocr_in_pdf(input_pdf, output_pdf):
|
|
try:
|
|
subprocess.run(["ocrmypdf", input_pdf, output_pdf], check=True)
|
|
return True
|
|
except Exception as e:
|
|
return False
|
|
|
|
def ocr_file_by_pages(file_path, api, model, prompt_key, update_progress, is_cancelled):
|
|
final_text = ""
|
|
if file_path.lower().endswith(".pdf"):
|
|
try:
|
|
pages = convert_from_path(file_path)
|
|
except Exception as e:
|
|
return f"❌ Error processing PDF: {str(e)}"
|
|
total = len(pages)
|
|
for i, page in enumerate(pages, start=1):
|
|
if is_cancelled():
|
|
update_progress(0, "⏹️ Cancelled")
|
|
return "Process cancelled."
|
|
temp_filename = os.path.join(OUTPUT_FOLDER, f"temp_page_{uuid.uuid4().hex}.png")
|
|
page.save(temp_filename, "PNG")
|
|
page_text = call_api_ocr(api, model, temp_filename, prompt_key)
|
|
final_text += f"[Page {i:04d}]\n{page_text}\n\n"
|
|
os.remove(temp_filename)
|
|
progress = int((i / total) * 100)
|
|
update_progress(progress, f"📄 Processed page {i} of {total}.")
|
|
time.sleep(1)
|
|
return final_text
|
|
else:
|
|
return call_api_ocr(api, model, file_path, prompt_key)
|
|
|
|
def translate_file_by_pages(file_path, api, model, target_language, prompt_key, update_progress, is_cancelled):
|
|
final_translation = ""
|
|
if file_path.lower().endswith(".pdf"):
|
|
try:
|
|
pages = convert_from_path(file_path)
|
|
except Exception as e:
|
|
return f"❌ Error processing PDF: {str(e)}"
|
|
total = len(pages)
|
|
for i, page in enumerate(pages, start=1):
|
|
if is_cancelled():
|
|
update_progress(0, "⏹️ Cancelled")
|
|
return "Process cancelled."
|
|
temp_filename = os.path.join(OUTPUT_FOLDER, f"temp_page_{uuid.uuid4().hex}.png")
|
|
page.save(temp_filename, "PNG")
|
|
page_text = pytesseract.image_to_string(page, lang='eng')
|
|
translated_page = call_api_translation(api, model, page_text, target_language, prompt_key)
|
|
final_translation += f"[Page {i:04d}]\n{translated_page}\n\n"
|
|
os.remove(temp_filename)
|
|
progress = int((i / total) * 100)
|
|
update_progress(progress, f"📄 Processed page {i} of {total}.")
|
|
time.sleep(1)
|
|
return final_translation
|
|
elif file_path.lower().endswith(".txt"):
|
|
with open(file_path, "r", encoding="utf-8") as f:
|
|
text = f.read()
|
|
translated = call_api_translation(api, model, text, target_language, prompt_key)
|
|
update_progress(100, "🎉 Process completed")
|
|
return f"[Page {1:04d}]\n{translated}"
|
|
else:
|
|
return "Unsupported file type for translation."
|
|
|
|
def process_file(file_path, api, model, mode, prompt_key, update_progress, is_cancelled):
|
|
"""
|
|
NOTA: Ahora solo usamos [Page XXXX] al inicio de cada página (por Tesseract).
|
|
"""
|
|
if is_cancelled():
|
|
update_progress(0, "⏹️ Cancelled")
|
|
return "Process cancelled."
|
|
update_progress(25, "📤 File uploaded.")
|
|
base_name = os.path.splitext(os.path.basename(file_path))[0]
|
|
|
|
if mode == "OCR":
|
|
processed_text = run_tesseract(file_path)
|
|
if is_cancelled():
|
|
update_progress(25, "⏹️ Cancelled")
|
|
return "Process cancelled."
|
|
update_progress(50, "✅ Tesseract OCR completed.")
|
|
pdf_output = os.path.join(OUTPUT_FOLDER, base_name + "_ocr.pdf")
|
|
if file_path.lower().endswith(".pdf"):
|
|
if embed_ocr_in_pdf(file_path, pdf_output):
|
|
update_progress(95, "📄 OCR embedded into PDF.")
|
|
else:
|
|
shutil.copy(file_path, pdf_output)
|
|
update_progress(95, "⚠️ Failed to embed OCR; original PDF copied.")
|
|
else:
|
|
shutil.copy(file_path, pdf_output)
|
|
|
|
elif mode == "OCR + AI":
|
|
processed_text = run_tesseract(file_path)
|
|
if is_cancelled():
|
|
update_progress(25, "⏹️ Cancelled")
|
|
return "Process cancelled."
|
|
update_progress(50, "✅ Tesseract OCR completed.")
|
|
processed_text = call_api_correction(api, model, processed_text, prompt_key)
|
|
pdf_output = os.path.join(OUTPUT_FOLDER, base_name + "_ocr.pdf")
|
|
if file_path.lower().endswith(".pdf"):
|
|
if embed_ocr_in_pdf(file_path, pdf_output):
|
|
update_progress(95, "📄 OCR embedded into PDF.")
|
|
else:
|
|
shutil.copy(file_path, pdf_output)
|
|
update_progress(95, "⚠️ Failed to embed OCR; original PDF copied.")
|
|
else:
|
|
shutil.copy(file_path, pdf_output)
|
|
|
|
elif mode == "AI":
|
|
update_progress(25, "📂 File ready for full AI processing.")
|
|
if file_path.lower().endswith(".pdf"):
|
|
processed_text = ocr_file_by_pages(file_path, api, model, prompt_key, update_progress, is_cancelled)
|
|
else:
|
|
processed_text = call_api_ocr(api, model, file_path, prompt_key)
|
|
|
|
else:
|
|
processed_text = "Unrecognized processing mode."
|
|
update_progress(25, "❌ Error: Unrecognized mode.")
|
|
|
|
if is_cancelled():
|
|
update_progress(75, "⏹️ Cancelled")
|
|
return "Process cancelled."
|
|
|
|
update_progress(75, "🤖 API processing completed.")
|
|
|
|
# Guardar el TXT final
|
|
txt_file = os.path.join(OUTPUT_FOLDER, base_name + ".txt")
|
|
with open(txt_file, "w", encoding="utf-8") as f:
|
|
f.write(processed_text)
|
|
|
|
update_progress(100, "🎉 Process completed")
|
|
return processed_text
|