Update app.py
Browse files
app.py
CHANGED
|
@@ -2,12 +2,14 @@ import gradio as gr
|
|
| 2 |
import pytesseract
|
| 3 |
from pytesseract import Output
|
| 4 |
from PIL import Image
|
|
|
|
| 5 |
|
| 6 |
def validate_chat_origin(image):
|
| 7 |
if image is None:
|
| 8 |
return {"valid": False, "error": "No image uploaded"}
|
| 9 |
|
| 10 |
try:
|
|
|
|
| 11 |
data = pytesseract.image_to_data(image, output_type=Output.DICT)
|
| 12 |
except Exception as e:
|
| 13 |
return {"valid": False, "error": str(e)}
|
|
@@ -15,102 +17,128 @@ def validate_chat_origin(image):
|
|
| 15 |
img_w, img_h = image.size
|
| 16 |
center_x = img_w / 2
|
| 17 |
|
| 18 |
-
# --- STEP 1:
|
| 19 |
-
# We look for
|
| 20 |
-
# The chat
|
| 21 |
-
barrier_keywords = [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
chat_start_y = 0
|
| 24 |
|
| 25 |
n_boxes = len(data['text'])
|
| 26 |
for i in range(n_boxes):
|
| 27 |
text = data['text'][i].lower().strip()
|
| 28 |
-
|
|
|
|
|
|
|
| 29 |
|
| 30 |
-
#
|
| 31 |
-
|
| 32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
element_bottom = data['top'][i] + data['height'][i]
|
|
|
|
|
|
|
| 34 |
if element_bottom > chat_start_y:
|
| 35 |
-
chat_start_y = element_bottom
|
| 36 |
|
| 37 |
-
#
|
| 38 |
-
if chat_start_y
|
|
|
|
|
|
|
|
|
|
| 39 |
chat_start_y = img_h * 0.20
|
| 40 |
|
| 41 |
-
# --- STEP 2: ANALYZE
|
|
|
|
|
|
|
| 42 |
valid_lines = []
|
| 43 |
|
| 44 |
-
#
|
| 45 |
-
ignore_words = ["today", "yesterday", "
|
| 46 |
|
| 47 |
for i in range(n_boxes):
|
| 48 |
text = data['text'][i].strip()
|
| 49 |
conf = int(data['conf'][i])
|
|
|
|
| 50 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
if not text or conf < 30: continue
|
| 52 |
|
| 53 |
-
# FILTER
|
| 54 |
-
if data['top'][i] < chat_start_y: continue
|
| 55 |
-
|
| 56 |
-
# FILTER 2: Ignore system words
|
| 57 |
if any(w in text.lower() for w in ignore_words): continue
|
|
|
|
|
|
|
|
|
|
| 58 |
|
| 59 |
valid_lines.append({
|
| 60 |
"text": text,
|
| 61 |
"left": data['left'][i],
|
| 62 |
-
"top":
|
| 63 |
"width": data['width'][i],
|
| 64 |
-
"
|
| 65 |
})
|
| 66 |
|
| 67 |
-
# Sort
|
| 68 |
valid_lines.sort(key=lambda x: x['top'])
|
| 69 |
|
| 70 |
final_decision = None
|
| 71 |
debug_log = []
|
| 72 |
-
debug_log.append(f"Header Barrier
|
| 73 |
|
| 74 |
for line in valid_lines:
|
| 75 |
-
#
|
| 76 |
-
|
| 77 |
-
deviation = abs(
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
# The name "Nithish" or dates like "20:18" (if centered) are NOT messages.
|
| 81 |
-
# If text is in the middle 20% of the screen, SKIP IT.
|
| 82 |
-
if deviation < (img_w * 0.10):
|
| 83 |
-
debug_log.append(f"Skipped CENTERED Text: '{line['text']}'")
|
| 84 |
continue
|
| 85 |
|
| 86 |
-
# ---
|
| 87 |
-
# If we are here,
|
| 88 |
|
| 89 |
-
# Stranger
|
| 90 |
-
if line[
|
| 91 |
final_decision = True
|
| 92 |
-
debug_log.append(f"✅ VALID (Stranger): '{line['text']}'
|
| 93 |
break
|
| 94 |
|
| 95 |
-
#
|
| 96 |
-
|
| 97 |
-
elif line["left"] > (img_w * 0.40):
|
| 98 |
final_decision = False
|
| 99 |
-
debug_log.append(f"❌ INVALID (Me): '{line['text']}'
|
| 100 |
break
|
| 101 |
|
|
|
|
| 102 |
if final_decision is True:
|
| 103 |
return {"valid": True, "reason": "First message is from stranger.", "debug": debug_log}
|
| 104 |
elif final_decision is False:
|
| 105 |
return {"valid": False, "reason": "First message is from you.", "debug": debug_log}
|
| 106 |
else:
|
| 107 |
-
return {"valid": False, "reason": "No
|
| 108 |
|
| 109 |
iface = gr.Interface(
|
| 110 |
fn=validate_chat_origin,
|
| 111 |
inputs=gr.Image(type="pil"),
|
| 112 |
outputs=gr.JSON(),
|
| 113 |
-
title="WhatsApp Validator
|
| 114 |
)
|
| 115 |
|
| 116 |
if __name__ == "__main__":
|
|
|
|
| 2 |
import pytesseract
|
| 3 |
from pytesseract import Output
|
| 4 |
from PIL import Image
|
| 5 |
+
import re
|
| 6 |
|
| 7 |
def validate_chat_origin(image):
|
| 8 |
if image is None:
|
| 9 |
return {"valid": False, "error": "No image uploaded"}
|
| 10 |
|
| 11 |
try:
|
| 12 |
+
# Get OCR Data with bounding boxes
|
| 13 |
data = pytesseract.image_to_data(image, output_type=Output.DICT)
|
| 14 |
except Exception as e:
|
| 15 |
return {"valid": False, "error": str(e)}
|
|
|
|
| 17 |
img_w, img_h = image.size
|
| 18 |
center_x = img_w / 2
|
| 19 |
|
| 20 |
+
# --- STEP 1: DEFINE HEADER BARRIERS ---
|
| 21 |
+
# We look for words that ALWAYS appear in the header/profile section.
|
| 22 |
+
# The true chat (Hi, Hello, etc.) will always be BELOW these.
|
| 23 |
+
barrier_keywords = [
|
| 24 |
+
"block", "add to contacts", "report", "business account",
|
| 25 |
+
"joined in", "not a contact", "encrypted", "end-to-end",
|
| 26 |
+
"security code", "waiting for this message", "invite link",
|
| 27 |
+
"group", "created this group", "you're both in", "spam"
|
| 28 |
+
]
|
| 29 |
|
| 30 |
+
# Regex to catch phone numbers (header info) like +94 71...
|
| 31 |
+
phone_pattern = re.compile(r'\+\d{2,3}\s?\d{2}')
|
| 32 |
+
|
| 33 |
+
# Find the LOWEST header element to set our "Safe Start Line"
|
| 34 |
chat_start_y = 0
|
| 35 |
|
| 36 |
n_boxes = len(data['text'])
|
| 37 |
for i in range(n_boxes):
|
| 38 |
text = data['text'][i].lower().strip()
|
| 39 |
+
conf = int(data['conf'][i])
|
| 40 |
+
|
| 41 |
+
if not text or conf < 30: continue
|
| 42 |
|
| 43 |
+
# Check if this word is a known header keyword
|
| 44 |
+
is_header_word = any(k in text for k in barrier_keywords)
|
| 45 |
+
|
| 46 |
+
# Check if it looks like the profile phone number
|
| 47 |
+
is_phone = phone_pattern.search(text)
|
| 48 |
+
|
| 49 |
+
if is_header_word or is_phone:
|
| 50 |
+
# We found a header element!
|
| 51 |
+
# The chat must start BELOW this element.
|
| 52 |
element_bottom = data['top'][i] + data['height'][i]
|
| 53 |
+
|
| 54 |
+
# Update our barrier to be the lowest one found so far
|
| 55 |
if element_bottom > chat_start_y:
|
| 56 |
+
chat_start_y = element_bottom
|
| 57 |
|
| 58 |
+
# Add a safety buffer (25px) below the lowest header element
|
| 59 |
+
if chat_start_y > 0:
|
| 60 |
+
chat_start_y += 25
|
| 61 |
+
else:
|
| 62 |
+
# Fallback: If OCR missed 'Block'/'Encrypted', skip top 20% of screen
|
| 63 |
chat_start_y = img_h * 0.20
|
| 64 |
|
| 65 |
+
# --- STEP 2: ANALYZE CHAT CONTENT ---
|
| 66 |
+
# Now we only look at text BELOW 'chat_start_y'
|
| 67 |
+
|
| 68 |
valid_lines = []
|
| 69 |
|
| 70 |
+
# Words to ignore inside the chat area (System dates/info)
|
| 71 |
+
ignore_words = ["today", "yesterday", "unread", "messages", "calls"]
|
| 72 |
|
| 73 |
for i in range(n_boxes):
|
| 74 |
text = data['text'][i].strip()
|
| 75 |
conf = int(data['conf'][i])
|
| 76 |
+
top = data['top'][i]
|
| 77 |
|
| 78 |
+
# FILTER A: Must be BELOW the calculated Header Barrier
|
| 79 |
+
if top < chat_start_y:
|
| 80 |
+
continue
|
| 81 |
+
|
| 82 |
+
# FILTER B: Ignore empty/low conf
|
| 83 |
if not text or conf < 30: continue
|
| 84 |
|
| 85 |
+
# FILTER C: Ignore common system words
|
|
|
|
|
|
|
|
|
|
| 86 |
if any(w in text.lower() for w in ignore_words): continue
|
| 87 |
+
|
| 88 |
+
# FILTER D: Ignore single symbols (OCR noise)
|
| 89 |
+
if len(text) == 1 and not text.isalnum(): continue
|
| 90 |
|
| 91 |
valid_lines.append({
|
| 92 |
"text": text,
|
| 93 |
"left": data['left'][i],
|
| 94 |
+
"top": top,
|
| 95 |
"width": data['width'][i],
|
| 96 |
+
"center": data['left'][i] + (data['width'][i] / 2)
|
| 97 |
})
|
| 98 |
|
| 99 |
+
# Sort lines Top-to-Bottom
|
| 100 |
valid_lines.sort(key=lambda x: x['top'])
|
| 101 |
|
| 102 |
final_decision = None
|
| 103 |
debug_log = []
|
| 104 |
+
debug_log.append(f"Header Barrier applied at Y={chat_start_y} (Ignored everything above)")
|
| 105 |
|
| 106 |
for line in valid_lines:
|
| 107 |
+
# --- CHECK 1: CENTER ALIGNMENT ---
|
| 108 |
+
# If text is in the middle 15% of the screen, it's system text (Date/Info).
|
| 109 |
+
deviation = abs(line['center'] - center_x)
|
| 110 |
+
if deviation < (img_w * 0.15):
|
| 111 |
+
debug_log.append(f"Skipped CENTERED: '{line['text']}'")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
continue
|
| 113 |
|
| 114 |
+
# --- CHECK 2: LEFT vs RIGHT ---
|
| 115 |
+
# If we are here, text is NOT centered. It's a message.
|
| 116 |
|
| 117 |
+
# Stranger Limit: Starts within first 18% of screen
|
| 118 |
+
if line['left'] < (img_w * 0.18):
|
| 119 |
final_decision = True
|
| 120 |
+
debug_log.append(f"✅ VALID (Stranger): '{line['text']}' found at Left Margin ({line['left']}px)")
|
| 121 |
break
|
| 122 |
|
| 123 |
+
# Me Limit: Starts after 40% of screen (safe assumption for right-aligned)
|
| 124 |
+
elif line['left'] > (img_w * 0.40):
|
|
|
|
| 125 |
final_decision = False
|
| 126 |
+
debug_log.append(f"❌ INVALID (Me): '{line['text']}' found at Right/Indented ({line['left']}px)")
|
| 127 |
break
|
| 128 |
|
| 129 |
+
# --- FINAL OUTPUT ---
|
| 130 |
if final_decision is True:
|
| 131 |
return {"valid": True, "reason": "First message is from stranger.", "debug": debug_log}
|
| 132 |
elif final_decision is False:
|
| 133 |
return {"valid": False, "reason": "First message is from you.", "debug": debug_log}
|
| 134 |
else:
|
| 135 |
+
return {"valid": False, "reason": "No valid chat messages found (Only system/header text).", "debug": debug_log}
|
| 136 |
|
| 137 |
iface = gr.Interface(
|
| 138 |
fn=validate_chat_origin,
|
| 139 |
inputs=gr.Image(type="pil"),
|
| 140 |
outputs=gr.JSON(),
|
| 141 |
+
title="WhatsApp Validator V11 (Dynamic Barrier)"
|
| 142 |
)
|
| 143 |
|
| 144 |
if __name__ == "__main__":
|