Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -4,24 +4,21 @@ from PIL import Image
|
|
| 4 |
import numpy as np
|
| 5 |
import google.generativeai as genai
|
| 6 |
import time
|
| 7 |
-
from gradio_client import Client, handle_file
|
| 8 |
-
import requests
|
| 9 |
-
|
| 10 |
-
import
|
| 11 |
|
| 12 |
# --- Configuration ---
|
| 13 |
-
# Gemini API key
|
| 14 |
-
|
| 15 |
-
GEMINI_API_KEY = "AIzaSyAKI92YawOKQ1-HRLmvaryMEWk_y4alJgA"
|
| 16 |
-
|
| 17 |
-
# One-API Token for Instagram Downloader
|
| 18 |
-
# IMPORTANT: Replace with your token from one-api.ir
|
| 19 |
-
ONE_API_TOKEN = "268976:66f4f58a2a905"
|
| 20 |
|
| 21 |
# URL to your background image hosted on your Hugging Face Space.
|
|
|
|
|
|
|
| 22 |
BACKGROUND_IMAGE_URL = "1.jpg"
|
| 23 |
|
| 24 |
-
# Global reader - initialize once
|
| 25 |
reader = None
|
| 26 |
|
| 27 |
def initialize_reader():
|
|
@@ -33,89 +30,6 @@ def initialize_reader():
|
|
| 33 |
print("EasyOCR model loaded successfully!")
|
| 34 |
return reader
|
| 35 |
|
| 36 |
-
# --- MODIFIED FUNCTION TO DOWNLOAD FROM INSTAGRAM USING SHORTCODE ---
|
| 37 |
-
def download_instagram_image(url):
|
| 38 |
-
"""
|
| 39 |
-
Downloads the first image from an Instagram post URL using One-API by extracting the shortcode.
|
| 40 |
-
Returns a PIL Image object on success or an error message string on failure.
|
| 41 |
-
"""
|
| 42 |
-
if not url or not url.strip():
|
| 43 |
-
return "Please enter an Instagram URL."
|
| 44 |
-
|
| 45 |
-
# Regex to extract the shortcode from various Instagram URL formats
|
| 46 |
-
# It looks for patterns like /p/..., /reel/..., or /tv/...
|
| 47 |
-
# Example: https://www.instagram.com/p/DMaqqN_RuqQ/ -> extracts 'DMaqqN_RuqQ'
|
| 48 |
-
match = re.search(r"/(?:p|reel|tv)/([a-zA-Z0-9_-]+)", url.strip())
|
| 49 |
-
|
| 50 |
-
if not match:
|
| 51 |
-
print(f"ERROR: Could not extract shortcode from URL: {url.strip()}")
|
| 52 |
-
return "Invalid Instagram URL format. Please provide a valid post or reel URL."
|
| 53 |
-
|
| 54 |
-
shortcode = match.group(1)
|
| 55 |
-
print(f"DEBUG: Extracted shortcode: {shortcode}")
|
| 56 |
-
|
| 57 |
-
# Use the new API endpoint with the 'shortcode' parameter, as per your curl example
|
| 58 |
-
api_endpoint = f"https://api.one-api.ir/instagram/v1/post/?shortcode={shortcode}"
|
| 59 |
-
|
| 60 |
-
print(f"DEBUG: Calling One-API with endpoint: {api_endpoint}")
|
| 61 |
-
|
| 62 |
-
try:
|
| 63 |
-
# Set headers as specified in the curl command, especially the token
|
| 64 |
-
headers = {
|
| 65 |
-
'one-api-token': ONE_API_TOKEN,
|
| 66 |
-
'accept': 'application/json'
|
| 67 |
-
}
|
| 68 |
-
response = requests.get(api_endpoint, headers=headers, timeout=30)
|
| 69 |
-
response.raise_for_status() # This will raise an HTTPError for 4xx or 5xx status codes
|
| 70 |
-
|
| 71 |
-
data = response.json()
|
| 72 |
-
|
| 73 |
-
# Check for success and find the image URL in the response
|
| 74 |
-
if data.get("ok") and data.get("result"):
|
| 75 |
-
media_list = data["result"].get("media", [])
|
| 76 |
-
image_url = None
|
| 77 |
-
# Find the first item that is an image
|
| 78 |
-
for item in media_list:
|
| 79 |
-
if item.get("type") == "image":
|
| 80 |
-
image_url = item.get("url")
|
| 81 |
-
break
|
| 82 |
-
|
| 83 |
-
if not image_url:
|
| 84 |
-
return "API Error: No image found in the post media."
|
| 85 |
-
else:
|
| 86 |
-
error_message = data.get("message", "Unknown API error.")
|
| 87 |
-
print(f"ERROR: One-API call failed. Message: {error_message}")
|
| 88 |
-
return f"API Error: {error_message}"
|
| 89 |
-
|
| 90 |
-
print(f"DEBUG: Found image URL: {image_url[:60]}...")
|
| 91 |
-
|
| 92 |
-
# Download the actual image content from the URL found
|
| 93 |
-
image_response = requests.get(image_url, timeout=30)
|
| 94 |
-
image_response.raise_for_status()
|
| 95 |
-
|
| 96 |
-
# Open the image from the downloaded bytes and return it as a PIL Image object
|
| 97 |
-
image = Image.open(io.BytesIO(image_response.content))
|
| 98 |
-
print("DEBUG: Instagram image downloaded and converted to PIL Image successfully.")
|
| 99 |
-
return image
|
| 100 |
-
|
| 101 |
-
except requests.exceptions.HTTPError as e:
|
| 102 |
-
# This will catch errors like 403 Forbidden, 404 Not Found, etc.
|
| 103 |
-
error_details = f"API Error ({e.response.status_code}): {e.response.reason}."
|
| 104 |
-
try:
|
| 105 |
-
# Try to get more specific error message from the API's JSON response
|
| 106 |
-
error_json = e.response.json()
|
| 107 |
-
error_details += f" Details: {error_json.get('message', 'No additional details.')}"
|
| 108 |
-
except requests.exceptions.JSONDecodeError:
|
| 109 |
-
error_details += f" Raw response: {e.response.text}"
|
| 110 |
-
print(f"ERROR: HTTP error occurred: {error_details}")
|
| 111 |
-
return error_details
|
| 112 |
-
except requests.exceptions.RequestException as e:
|
| 113 |
-
print(f"ERROR: Network error while contacting API or downloading image: {e}")
|
| 114 |
-
return f"Network Error: Could not retrieve data. Please check the connection. Details: {e}"
|
| 115 |
-
except Exception as e:
|
| 116 |
-
print(f"ERROR: An unexpected error occurred in download_instagram_image: {e}")
|
| 117 |
-
return f"An unexpected error occurred: {str(e)}"
|
| 118 |
-
|
| 119 |
def extract_text_from_quote(image):
|
| 120 |
"""Extract text from quote image using EasyOCR"""
|
| 121 |
if image is None:
|
|
@@ -124,11 +38,9 @@ def extract_text_from_quote(image):
|
|
| 124 |
try:
|
| 125 |
reader = initialize_reader()
|
| 126 |
img_array = np.array(image)
|
| 127 |
-
# Using paragraph=True helps group related lines of text
|
| 128 |
results = reader.readtext(img_array, paragraph=True)
|
| 129 |
|
| 130 |
if results:
|
| 131 |
-
# Join all detected text blocks with a space
|
| 132 |
text_parts = [result[1].strip() for result in results if len(result) >= 2 and result[1].strip()]
|
| 133 |
if text_parts:
|
| 134 |
extracted_text = ' '.join(text_parts)
|
|
@@ -146,12 +58,13 @@ def translate_extracted(text, lang):
|
|
| 146 |
return "No valid text to translate."
|
| 147 |
|
| 148 |
try:
|
| 149 |
-
print(f"DEBUG: API Key loaded. Starting translation to {lang}")
|
| 150 |
-
genai.configure(api_key=
|
| 151 |
|
| 152 |
-
for attempt in range(3):
|
| 153 |
try:
|
| 154 |
model = genai.GenerativeModel('gemini-1.5-flash')
|
|
|
|
| 155 |
prompt = f"""
|
| 156 |
You are a cool, chill translator with a fun and warm personality, inspired by Persian Twitter style.
|
| 157 |
Your translations should be natural, slangy, and relatable. Use colloquial words and contractions.
|
|
@@ -179,13 +92,15 @@ def translate_extracted(text, lang):
|
|
| 179 |
print(f"DEBUG: Translation failed: {str(e)}")
|
| 180 |
return error_msg
|
| 181 |
|
|
|
|
| 182 |
def overlay_text_on_image(translated_text):
|
| 183 |
"""
|
| 184 |
Sends translated text to the 'textoverimage1' Space and gets the resulting image.
|
| 185 |
"""
|
|
|
|
| 186 |
if not translated_text or "Error" in translated_text or "No valid text" in translated_text:
|
| 187 |
print("DEBUG: Skipping image overlay due to invalid translated text.")
|
| 188 |
-
return None
|
| 189 |
|
| 190 |
try:
|
| 191 |
print("DEBUG: Initializing client for 'kavehtaheri/textoverimage1'")
|
|
@@ -195,12 +110,13 @@ def overlay_text_on_image(translated_text):
|
|
| 195 |
print(f"DEBUG: Persian Text: {translated_text}")
|
| 196 |
print(f"DEBUG: Image URL: {BACKGROUND_IMAGE_URL}")
|
| 197 |
|
|
|
|
| 198 |
result_image_path = client.predict(
|
| 199 |
persian_text=translated_text,
|
| 200 |
-
url="",
|
| 201 |
upload=handle_file(BACKGROUND_IMAGE_URL),
|
| 202 |
-
username="",
|
| 203 |
-
text_color="Black",
|
| 204 |
api_name="/overlay_text_on_image"
|
| 205 |
)
|
| 206 |
|
|
@@ -209,123 +125,156 @@ def overlay_text_on_image(translated_text):
|
|
| 209 |
|
| 210 |
except Exception as e:
|
| 211 |
print(f"ERROR: Could not get image from 'textoverimage1' space. Error: {e}")
|
|
|
|
|
|
|
| 212 |
return None
|
| 213 |
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
def process_everything(image, lang):
|
| 220 |
-
"""The core pipeline: OCR -> Translate -> Overlay. Accepts a PIL image."""
|
| 221 |
-
if image is None:
|
| 222 |
-
return "Please provide an image first.", "Words: 0", "Translation failed: No image provided.", None
|
| 223 |
-
|
| 224 |
-
text, wc = extract_text_from_quote(image)
|
| 225 |
-
if "No text" in text or "Error" in text:
|
| 226 |
-
return text, wc, "Translation failed: No text extracted.", None
|
| 227 |
|
| 228 |
-
|
| 229 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 230 |
|
| 231 |
-
|
|
|
|
|
|
|
| 232 |
|
| 233 |
-
def
|
| 234 |
-
"""
|
| 235 |
-
|
| 236 |
-
download_result = download_instagram_image(url)
|
| 237 |
-
|
| 238 |
-
# Check if the download function returned an error string
|
| 239 |
-
if isinstance(download_result, str):
|
| 240 |
-
# The download failed, show an error and stop the process
|
| 241 |
-
gr.Error(f"Download Failed: {download_result}")
|
| 242 |
-
return download_result, "Words: 0", "Process failed.", None, None
|
| 243 |
-
|
| 244 |
-
# If successful, it returned a PIL image. Now process it.
|
| 245 |
-
gr.Info("Image downloaded! Starting OCR and Translation...")
|
| 246 |
-
text, wc, translated, final_image = process_everything(download_result, lang)
|
| 247 |
-
|
| 248 |
-
# Return the downloaded image to populate the input image box for user reference
|
| 249 |
-
return text, wc, translated, final_image, download_result
|
| 250 |
|
| 251 |
# --- Gradio Interface ---
|
| 252 |
with gr.Blocks(title="Quote OCR & Overlay", theme=gr.themes.Soft()) as demo:
|
| 253 |
|
| 254 |
gr.Markdown("# 📝 Quote Text Extractor & Image Generator")
|
| 255 |
-
gr.Markdown("
|
| 256 |
|
| 257 |
with gr.Row():
|
| 258 |
# --- INPUT COLUMN ---
|
| 259 |
with gr.Column(scale=1):
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
insta_url_input = gr.Textbox(label="Instagram Post URL", placeholder="Paste a link like https://www.instagram.com/p/C0123ABCD.../")
|
| 267 |
-
insta_process_btn = gr.Button("Download & Process", variant="primary")
|
| 268 |
-
|
| 269 |
target_lang = gr.Dropdown(
|
| 270 |
label="Target Language",
|
| 271 |
-
choices=["persian(farsi)"],
|
| 272 |
value="persian(farsi)",
|
| 273 |
-
interactive=False
|
| 274 |
)
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
clear_btn = gr.Button("Clear All", variant="secondary")
|
| 278 |
-
extract_btn = gr.Button("Process Uploaded Image", variant="primary")
|
| 279 |
-
|
| 280 |
# --- OUTPUTS COLUMN ---
|
| 281 |
with gr.Column(scale=2):
|
| 282 |
text_output = gr.Textbox(label="2. Extracted English Text", placeholder="Extracted text appears here...", lines=4, show_copy_button=True)
|
| 283 |
word_count = gr.Textbox(label="Word Count", interactive=False, max_lines=1)
|
| 284 |
translated_output = gr.Textbox(label="3. Translated Persian Text", placeholder="Persian translation appears here...", lines=4, show_copy_button=True)
|
| 285 |
-
|
|
|
|
|
|
|
| 286 |
final_image_output = gr.Image(label="4. Final Image with Text Overlay", type="filepath")
|
| 287 |
|
| 288 |
# --- Event Handlers ---
|
| 289 |
|
| 290 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 291 |
extract_btn.click(
|
| 292 |
fn=process_everything,
|
| 293 |
-
inputs=[image_input, target_lang],
|
| 294 |
outputs=[text_output, word_count, translated_output, final_image_output]
|
| 295 |
)
|
| 296 |
|
| 297 |
-
#
|
| 298 |
-
def auto_process_wrapper(image, lang, is_enabled):
|
| 299 |
-
if is_enabled:
|
| 300 |
-
return process_everything(image, lang)
|
| 301 |
-
# If auto-process is off, just do OCR and stop
|
| 302 |
-
text, wc = extract_text_from_quote(image)
|
| 303 |
-
return text, wc, "Translation will appear here...", None
|
| 304 |
-
|
| 305 |
image_input.change(
|
| 306 |
-
fn=
|
| 307 |
-
inputs=[image_input, target_lang,
|
| 308 |
outputs=[text_output, word_count, translated_output, final_image_output]
|
| 309 |
)
|
| 310 |
|
| 311 |
-
#
|
| 312 |
-
|
| 313 |
-
fn=
|
| 314 |
-
inputs=[
|
| 315 |
-
|
| 316 |
-
outputs=[text_output, word_count, translated_output, final_image_output, image_input]
|
| 317 |
)
|
| 318 |
|
| 319 |
-
#
|
| 320 |
clear_btn.click(
|
| 321 |
fn=clear_all,
|
| 322 |
-
outputs=[image_input,
|
| 323 |
)
|
| 324 |
|
| 325 |
-
gr.Markdown("### 💡 How It Works:\n1.
|
| 326 |
|
| 327 |
if __name__ == "__main__":
|
| 328 |
-
#
|
| 329 |
# gradio
|
| 330 |
# easyocr
|
| 331 |
# pillow
|
|
@@ -333,4 +282,4 @@ if __name__ == "__main__":
|
|
| 333 |
# google-generativeai
|
| 334 |
# gradio_client
|
| 335 |
# requests
|
| 336 |
-
demo.launch(
|
|
|
|
| 4 |
import numpy as np
|
| 5 |
import google.generativeai as genai
|
| 6 |
import time
|
| 7 |
+
from gradio_client import Client, handle_file # <-- ADDED IMPORT
|
| 8 |
+
import requests
|
| 9 |
+
from urllib.parse import urlparse
|
| 10 |
+
import io
|
| 11 |
|
| 12 |
# --- Configuration ---
|
| 13 |
+
# Gemini API key - It's better to use environment variables, but this works for now.
|
| 14 |
+
api_key = "AIzaSyAKI92YawOKQ1-HRLmvaryMEWk_y4alJgA"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
# URL to your background image hosted on your Hugging Face Space.
|
| 17 |
+
# IMPORTANT: Replace 'YOUR-HF-USERNAME/YOUR-SPACE-NAME' with your actual space details.
|
| 18 |
+
# The file '1.jpg' must be in the root of your Space's repository.
|
| 19 |
BACKGROUND_IMAGE_URL = "1.jpg"
|
| 20 |
|
| 21 |
+
# Global reader - initialize once
|
| 22 |
reader = None
|
| 23 |
|
| 24 |
def initialize_reader():
|
|
|
|
| 30 |
print("EasyOCR model loaded successfully!")
|
| 31 |
return reader
|
| 32 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
def extract_text_from_quote(image):
|
| 34 |
"""Extract text from quote image using EasyOCR"""
|
| 35 |
if image is None:
|
|
|
|
| 38 |
try:
|
| 39 |
reader = initialize_reader()
|
| 40 |
img_array = np.array(image)
|
|
|
|
| 41 |
results = reader.readtext(img_array, paragraph=True)
|
| 42 |
|
| 43 |
if results:
|
|
|
|
| 44 |
text_parts = [result[1].strip() for result in results if len(result) >= 2 and result[1].strip()]
|
| 45 |
if text_parts:
|
| 46 |
extracted_text = ' '.join(text_parts)
|
|
|
|
| 58 |
return "No valid text to translate."
|
| 59 |
|
| 60 |
try:
|
| 61 |
+
print(f"DEBUG: API Key loaded (first 5 chars: {api_key[:5]}...). Starting translation to {lang}")
|
| 62 |
+
genai.configure(api_key=api_key)
|
| 63 |
|
| 64 |
+
for attempt in range(3):
|
| 65 |
try:
|
| 66 |
model = genai.GenerativeModel('gemini-1.5-flash')
|
| 67 |
+
# Updated prompt for clarity and conciseness
|
| 68 |
prompt = f"""
|
| 69 |
You are a cool, chill translator with a fun and warm personality, inspired by Persian Twitter style.
|
| 70 |
Your translations should be natural, slangy, and relatable. Use colloquial words and contractions.
|
|
|
|
| 92 |
print(f"DEBUG: Translation failed: {str(e)}")
|
| 93 |
return error_msg
|
| 94 |
|
| 95 |
+
# NEW FUNCTION TO CALL THE SECOND HF SPACE
|
| 96 |
def overlay_text_on_image(translated_text):
|
| 97 |
"""
|
| 98 |
Sends translated text to the 'textoverimage1' Space and gets the resulting image.
|
| 99 |
"""
|
| 100 |
+
# Don't proceed if translation failed or is empty
|
| 101 |
if not translated_text or "Error" in translated_text or "No valid text" in translated_text:
|
| 102 |
print("DEBUG: Skipping image overlay due to invalid translated text.")
|
| 103 |
+
return None # Return None to clear the image output
|
| 104 |
|
| 105 |
try:
|
| 106 |
print("DEBUG: Initializing client for 'kavehtaheri/textoverimage1'")
|
|
|
|
| 110 |
print(f"DEBUG: Persian Text: {translated_text}")
|
| 111 |
print(f"DEBUG: Image URL: {BACKGROUND_IMAGE_URL}")
|
| 112 |
|
| 113 |
+
# The handle_file function downloads the URL to a temporary file for upload
|
| 114 |
result_image_path = client.predict(
|
| 115 |
persian_text=translated_text,
|
| 116 |
+
url="", # Pass an empty string for URL if upload is used
|
| 117 |
upload=handle_file(BACKGROUND_IMAGE_URL),
|
| 118 |
+
username="", # Not needed based on the API, can be empty
|
| 119 |
+
text_color="Black", # As specified in the screenshot
|
| 120 |
api_name="/overlay_text_on_image"
|
| 121 |
)
|
| 122 |
|
|
|
|
| 125 |
|
| 126 |
except Exception as e:
|
| 127 |
print(f"ERROR: Could not get image from 'textoverimage1' space. Error: {e}")
|
| 128 |
+
# Return a placeholder or raise an error in Gradio UI
|
| 129 |
+
# For now, we return None which will clear the output
|
| 130 |
return None
|
| 131 |
|
| 132 |
+
# NEW FUNCTION TO FETCH IMAGE FROM INSTAGRAM VIA ONE-API
|
| 133 |
+
def get_instagram_image(ig_url):
|
| 134 |
+
"""Fetch image from Instagram post using One-API."""
|
| 135 |
+
if not ig_url:
|
| 136 |
+
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
|
| 138 |
+
try:
|
| 139 |
+
# Extract shortcode from URL
|
| 140 |
+
parsed = urlparse(ig_url)
|
| 141 |
+
path = parsed.path.strip('/').split('/')
|
| 142 |
+
if 'p' in path:
|
| 143 |
+
idx = path.index('p')
|
| 144 |
+
if idx + 1 < len(path):
|
| 145 |
+
shortcode = path[idx + 1]
|
| 146 |
+
else:
|
| 147 |
+
raise ValueError("Invalid Instagram URL: No shortcode found.")
|
| 148 |
+
else:
|
| 149 |
+
raise ValueError("Invalid Instagram URL: No 'p' segment found.")
|
| 150 |
+
|
| 151 |
+
# One-API details
|
| 152 |
+
api_token = "268976:66f4f58a2a905"
|
| 153 |
+
api_url = f"https://api.one-api.ir/instagram/v1/post/?shortcode={shortcode}"
|
| 154 |
+
headers = {
|
| 155 |
+
"accept": "application/json",
|
| 156 |
+
"one-api-token": api_token
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
response = requests.get(api_url, headers=headers)
|
| 160 |
+
response.raise_for_status() # Raise if not 200
|
| 161 |
+
|
| 162 |
+
data = response.json()
|
| 163 |
+
|
| 164 |
+
# Adjust this based on actual API response structure (check docs: https://docs.one-api.ir/instagram)
|
| 165 |
+
# Assuming the image URL is in data['url'] or data['thumbnail_src'] (common for IG posts)
|
| 166 |
+
image_url = data.get('url') or data.get('thumbnail_src') or data.get('display_url')
|
| 167 |
+
if not image_url:
|
| 168 |
+
raise ValueError("No image URL found in API response.")
|
| 169 |
+
|
| 170 |
+
# Download the image
|
| 171 |
+
img_response = requests.get(image_url)
|
| 172 |
+
img_response.raise_for_status()
|
| 173 |
+
|
| 174 |
+
img = Image.open(io.BytesIO(img_response.content))
|
| 175 |
+
return img
|
| 176 |
|
| 177 |
+
except Exception as e:
|
| 178 |
+
print(f"ERROR fetching Instagram image: {str(e)}")
|
| 179 |
+
return None
|
| 180 |
|
| 181 |
+
def clear_all():
|
| 182 |
+
"""Clear all inputs and outputs"""
|
| 183 |
+
return None, None, "Your extracted quote will appear here...", "Words: 0", "Translation will appear here...", None, True # Clear image, ig_url, and reset checkbox
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 184 |
|
| 185 |
# --- Gradio Interface ---
|
| 186 |
with gr.Blocks(title="Quote OCR & Overlay", theme=gr.themes.Soft()) as demo:
|
| 187 |
|
| 188 |
gr.Markdown("# 📝 Quote Text Extractor & Image Generator")
|
| 189 |
+
gr.Markdown("Upload an image to extract text, translate it, and overlay it onto a new background.")
|
| 190 |
|
| 191 |
with gr.Row():
|
| 192 |
# --- INPUT COLUMN ---
|
| 193 |
with gr.Column(scale=1):
|
| 194 |
+
image_input = gr.Image(label="1. Upload Quote Image", type="pil", sources=["upload", "clipboard"])
|
| 195 |
+
ig_input = gr.Textbox(label="Or Instagram Link", placeholder="e.g., https://www.instagram.com/p/C-ODQjyy4N3/")
|
| 196 |
+
with gr.Row():
|
| 197 |
+
clear_btn = gr.Button("Clear All", variant="secondary")
|
| 198 |
+
extract_btn = gr.Button("Extract & Translate", variant="primary")
|
| 199 |
+
|
|
|
|
|
|
|
|
|
|
| 200 |
target_lang = gr.Dropdown(
|
| 201 |
label="Target Language",
|
| 202 |
+
choices=["persian(farsi)"], # Locked to Persian as per the logic
|
| 203 |
value="persian(farsi)",
|
| 204 |
+
interactive=False # Not changeable since the prompt is hardcoded for Persian
|
| 205 |
)
|
| 206 |
+
auto_translate = gr.Checkbox(label="Auto-Process After Upload", value=True)
|
| 207 |
+
|
|
|
|
|
|
|
|
|
|
| 208 |
# --- OUTPUTS COLUMN ---
|
| 209 |
with gr.Column(scale=2):
|
| 210 |
text_output = gr.Textbox(label="2. Extracted English Text", placeholder="Extracted text appears here...", lines=4, show_copy_button=True)
|
| 211 |
word_count = gr.Textbox(label="Word Count", interactive=False, max_lines=1)
|
| 212 |
translated_output = gr.Textbox(label="3. Translated Persian Text", placeholder="Persian translation appears here...", lines=4, show_copy_button=True)
|
| 213 |
+
|
| 214 |
+
gr.Markdown("---") # Separator
|
| 215 |
+
|
| 216 |
final_image_output = gr.Image(label="4. Final Image with Text Overlay", type="filepath")
|
| 217 |
|
| 218 |
# --- Event Handlers ---
|
| 219 |
|
| 220 |
+
# Combined function for the main button and auto-processing
|
| 221 |
+
def process_everything(image, ig_url, lang, auto_process_enabled):
|
| 222 |
+
if not auto_process_enabled:
|
| 223 |
+
# If auto-process is off, we still run extract but not the rest
|
| 224 |
+
text, wc = extract_text_from_quote(image)
|
| 225 |
+
return text, wc, "Translation will appear here...", None
|
| 226 |
+
|
| 227 |
+
# Determine source image: prioritize uploaded image if provided, else IG link
|
| 228 |
+
if image is not None:
|
| 229 |
+
source_image = image
|
| 230 |
+
elif ig_url:
|
| 231 |
+
source_image = get_instagram_image(ig_url)
|
| 232 |
+
if source_image is None:
|
| 233 |
+
return "Error fetching image from Instagram link.", "Words: 0", "Translation failed: No image fetched.", None
|
| 234 |
+
else:
|
| 235 |
+
return "Please upload an image or provide an Instagram link.", "Words: 0", "Translation failed: No input provided.", None
|
| 236 |
+
|
| 237 |
+
text, wc = extract_text_from_quote(source_image)
|
| 238 |
+
# Proceed only if text was found
|
| 239 |
+
if "No text" in text or "Error" in text:
|
| 240 |
+
return text, wc, "Translation failed: No text extracted.", None
|
| 241 |
+
|
| 242 |
+
translated = translate_extracted(text, lang)
|
| 243 |
+
final_image = overlay_text_on_image(translated)
|
| 244 |
+
|
| 245 |
+
return text, wc, translated, final_image
|
| 246 |
+
|
| 247 |
+
# The main button triggers the full pipeline
|
| 248 |
extract_btn.click(
|
| 249 |
fn=process_everything,
|
| 250 |
+
inputs=[image_input, ig_input, target_lang, gr.State(True)], # Pass True to force processing
|
| 251 |
outputs=[text_output, word_count, translated_output, final_image_output]
|
| 252 |
)
|
| 253 |
|
| 254 |
+
# Changing the image triggers the pipeline only if 'auto-translate' is checked
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
image_input.change(
|
| 256 |
+
fn=process_everything,
|
| 257 |
+
inputs=[image_input, ig_input, target_lang, auto_translate],
|
| 258 |
outputs=[text_output, word_count, translated_output, final_image_output]
|
| 259 |
)
|
| 260 |
|
| 261 |
+
# Changing the IG link triggers the pipeline only if 'auto-translate' is checked
|
| 262 |
+
ig_input.change(
|
| 263 |
+
fn=process_everything,
|
| 264 |
+
inputs=[image_input, ig_input, target_lang, auto_translate],
|
| 265 |
+
outputs=[text_output, word_count, translated_output, final_image_output]
|
|
|
|
| 266 |
)
|
| 267 |
|
| 268 |
+
# Clear button action
|
| 269 |
clear_btn.click(
|
| 270 |
fn=clear_all,
|
| 271 |
+
outputs=[image_input, ig_input, text_output, word_count, translated_output, final_image_output, auto_translate]
|
| 272 |
)
|
| 273 |
|
| 274 |
+
gr.Markdown("### 💡 How It Works:\n1. Upload a clear image containing English text OR provide an Instagram post link.\n2. The app automatically extracts the text using OCR.\n3. The text is translated to a casual, modern Persian.\n4. The Persian text is sent to a second app which overlays it on a background image.\n5. The final image is displayed.")
|
| 275 |
|
| 276 |
if __name__ == "__main__":
|
| 277 |
+
# Add requirements to your requirements.txt:
|
| 278 |
# gradio
|
| 279 |
# easyocr
|
| 280 |
# pillow
|
|
|
|
| 282 |
# google-generativeai
|
| 283 |
# gradio_client
|
| 284 |
# requests
|
| 285 |
+
demo.launch()
|