amreid commited on
Commit
6a3ea0f
·
verified ·
1 Parent(s): 4514932

gradio==3.40
Pillow
piexif
transformers
torch
torchvision
ffmpeg-python
face_recognition
numpy

Files changed (1) hide show
  1. app.py +211 -0
app.py ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py
2
+ import os
3
+ import io
4
+ import subprocess
5
+ from pathlib import Path
6
+ from datetime import datetime
7
+ import gradio as gr
8
+ from PIL import Image
9
+ import piexif
10
+ import tempfile
11
+ import base64
12
+ import requests
13
+
14
+ # For AI detection: example using a HF model via transformers
15
+ import torch
16
+ from torchvision import transforms
17
+ from PIL import Image
18
+
19
+ # ---- CONFIG ----
20
+ # If you want to call TinEye/Bing APIs, put keys here or use Spaces secrets.
21
+ TINEYE_API_KEY = os.environ.get("TINEYE_API_KEY","")
22
+ BING_API_KEY = os.environ.get("BING_API_KEY","")
23
+
24
+ HF_AI_MODEL = "Dafilab/ai-image-detector" # مثال؛ يمكن تغييره أو استخدام SuSy
25
+ IMG_SIZE = 380
26
+
27
+ # ---- helper utilities ----
28
+ def save_bytes_to_file(b, path):
29
+ with open(path, "wb") as f:
30
+ f.write(b)
31
+
32
+ def extract_exif(image_bytes):
33
+ try:
34
+ exif_dict = piexif.load(image_bytes)
35
+ # Convert to readable pairs where possible
36
+ res = {}
37
+ for ifd in exif_dict:
38
+ if not exif_dict[ifd]:
39
+ continue
40
+ res[ifd] = {}
41
+ for tag, val in exif_dict[ifd].items():
42
+ name = piexif.TAGS[ifd].get(tag, {"name": str(tag)})["name"]
43
+ res[ifd][name] = val
44
+ return res
45
+ except Exception as e:
46
+ return {"error": str(e)}
47
+
48
+ # ---- AI detector loader (simple) ----
49
+ def load_ai_model():
50
+ try:
51
+ from transformers import ViTImageProcessor, ViTForImageClassification
52
+ processor = ViTImageProcessor.from_pretrained(HF_AI_MODEL)
53
+ model = ViTForImageClassification.from_pretrained(HF_AI_MODEL)
54
+ model.eval()
55
+ return processor, model
56
+ except Exception as e:
57
+ print("Could not load HF model:", e)
58
+ return None, None
59
+
60
+ processor, hf_model = load_ai_model()
61
+
62
+ def detect_ai_image(pil_image):
63
+ if processor is None or hf_model is None:
64
+ return {"status":"model_not_loaded"}
65
+ inputs = processor(images=pil_image, return_tensors="pt")
66
+ with torch.no_grad():
67
+ outputs = hf_model(**inputs)
68
+ probs = torch.nn.functional.softmax(outputs.logits, dim=1)[0].tolist()
69
+ # label mapping may vary by model
70
+ labels = hf_model.config.id2label if hasattr(hf_model.config, "id2label") else {0:"REAL",1:"FAKE"}
71
+ top_idx = max(range(len(probs)), key=lambda i:probs[i])
72
+ return {"label": labels.get(top_idx, str(top_idx)), "confidence": float(probs[top_idx])}
73
+
74
+ # ---- video keyframes (requires ffmpeg available) ----
75
+ def extract_keyframes_from_video(video_path, max_frames=5):
76
+ out_dir = tempfile.mkdtemp()
77
+ # extract at most `max_frames` evenly spaced frames
78
+ # first count duration via ffprobe
79
+ try:
80
+ cmd = [
81
+ "ffprobe", "-v", "error", "-select_streams", "v:0",
82
+ "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1",
83
+ video_path
84
+ ]
85
+ proc = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
86
+ duration = float(proc.stdout.strip() or 0.0)
87
+ except Exception:
88
+ duration = 0
89
+ frames = []
90
+ if duration <= 0:
91
+ # fallback: extract first few frames
92
+ timestamps = [0, 1, 2, 3, 4][:max_frames]
93
+ else:
94
+ step = max(1, duration / max_frames)
95
+ timestamps = [i*step for i in range(max_frames)]
96
+ for i, t in enumerate(timestamps):
97
+ out_path = os.path.join(out_dir, f"frame_{i}.jpg")
98
+ cmd = ["ffmpeg", "-ss", str(t), "-i", video_path, "-frames:v", "1", "-q:v", "2", out_path, "-y"]
99
+ try:
100
+ subprocess.run(cmd, capture_output=True, timeout=15)
101
+ if os.path.exists(out_path):
102
+ frames.append(out_path)
103
+ except Exception:
104
+ continue
105
+ return frames
106
+
107
+ # ---- reverse search links generator ----
108
+ def build_reverse_search_links_for_file(file_url=None, local_file_path=None):
109
+ """
110
+ If file_url is provided (public URL), build direct links that open reverse image search with that URL.
111
+ If not, we will upload file temporarily to imgur anonymous (optional) or provide download blob.
112
+ Here we will prefer to return search-by-upload pages.
113
+ """
114
+ links = {}
115
+ if file_url:
116
+ # Google (open image search by URL), Yandex, TinEye, Bing
117
+ links['Google'] = f"https://www.google.com/searchbyimage?image_url={file_url}"
118
+ links['Yandex'] = f"https://yandex.com/images/search?rpt=imageview&url={file_url}"
119
+ links['TinEye'] = f"https://tineye.com/search?url={file_url}"
120
+ links['Bing'] = f"https://www.bing.com/images/search?q=imgurl:{file_url}&view=detailv2"
121
+ else:
122
+ # provide pages where user can upload the file manually
123
+ links['Google_upload'] = "https://images.google.com/ (use camera icon → upload image)"
124
+ links['TinEye_upload'] = "https://tineye.com/ (upload image)"
125
+ links['Yandex_upload'] = "https://yandex.com/images/ (upload image)"
126
+ links['Bing_upload'] = "https://www.bing.com/images (click camera)"
127
+ return links
128
+
129
+ # ---- face detection (simple cropping) ----
130
+ def detect_and_crop_faces(pil_image):
131
+ try:
132
+ import face_recognition
133
+ except Exception as e:
134
+ return {"error":"face_recognition_not_installed_or_failed"}
135
+ img = pil_image.convert("RGB")
136
+ arr = face_recognition.api.load_image_file(io.BytesIO())
137
+ # face_recognition expects a path or numpy array; workaround: convert
138
+ np_img = face_recognition.api.load_image_file(io.BytesIO(img.tobytes())) if False else None
139
+ # Simpler: use face_recognition.face_locations on PIL via numpy
140
+ import numpy as np
141
+ np_img = np.array(img)
142
+ locs = face_recognition.face_locations(np_img)
143
+ faces = []
144
+ for i, (top,right,bottom,left) in enumerate(locs):
145
+ crop = img.crop((left, top, right, bottom))
146
+ b = io.BytesIO()
147
+ crop.save(b, format="JPEG")
148
+ faces.append({'index':i, 'image_bytes': b.getvalue()})
149
+ return faces
150
+
151
+ # ---- main Gradio function ----
152
+ def process_upload(file):
153
+ # file: UploadedFile object from Gradio
154
+ fname = file.name
155
+ b = file.read()
156
+ out = {"filename": fname}
157
+ # if image
158
+ try:
159
+ pil = Image.open(io.BytesIO(b))
160
+ out['type'] = "image"
161
+ # EXIF
162
+ try:
163
+ exif = extract_exif(b)
164
+ out['exif'] = exif
165
+ except Exception as e:
166
+ out['exif'] = {"error": str(e)}
167
+ # AI detection
168
+ try:
169
+ ai_res = detect_ai_image(pil)
170
+ out['ai_detection'] = ai_res
171
+ except Exception as e:
172
+ out['ai_detection'] = {"error":str(e)}
173
+ # provide reverse-search links (no public URL available)
174
+ out['reverse_links'] = build_reverse_search_links_for_file()
175
+ # prepare preview
176
+ buf = io.BytesIO()
177
+ pil.thumbnail((800,800))
178
+ pil.save(buf, format="JPEG")
179
+ preview_b64 = base64.b64encode(buf.getvalue()).decode()
180
+ out['preview_base64'] = preview_b64
181
+ return out
182
+ except Exception:
183
+ # assume video
184
+ tv = tempfile.NamedTemporaryFile(delete=False, suffix=os.path.splitext(fname)[1])
185
+ tv.write(b)
186
+ tv.flush()
187
+ frames = extract_keyframes_from_video(tv.name, max_frames=5)
188
+ out['type'] = "video"
189
+ out['keyframes'] = []
190
+ for path in frames:
191
+ with open(path,"rb") as f:
192
+ out['keyframes'].append(base64.b64encode(f.read()).decode())
193
+ out['reverse_links'] = build_reverse_search_links_for_file()
194
+ return out
195
+
196
+ # ---- Gradio UI ----
197
+ css = """
198
+ .gradio-container { max-width: 1100px; margin: auto; }
199
+ """
200
+
201
+ with gr.Blocks(css=css) as demo:
202
+ gr.Markdown("## أداة تحقق صور/فيديو مبسطة (صحفيين)\n- ارفع صورة أو فيديو\n- سيعرض EXIF، كشف إذا كانت الصورة مولدة بالـAI (موديل HF إن تم تحميله)، ويفصل keyframes من الفيديو\n- يقدّم روابط سريعة للبحث العكسي (افتحها لتفقد أول ظهور على الويب)\n")
203
+ with gr.Row():
204
+ inp = gr.File(label="رفع صورة أو فيديو (JPEG/PNG/MP4...)")
205
+ btn = gr.Button("تحقق")
206
+ out_json = gr.JSON(label="نتيجة الفحص (JSON)")
207
+ preview = gr.Image(label="معاينة / keyframes", interactive=False)
208
+ btn.click(process_upload, inputs=inp, outputs=out_json)
209
+
210
+ if __name__ == "__main__":
211
+ demo.launch()