Nymbo commited on
Commit
a8f7368
·
verified ·
1 Parent(s): c6fe582

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +247 -140
app.py CHANGED
@@ -1,178 +1,285 @@
1
  import gradio as gr
2
- import requests
3
  import yt_dlp
4
  import cv2
5
  from google_img_source_search import ReverseImageSearcher
6
  from PIL import Image
7
- import os
8
  import uuid
9
- import subprocess
 
 
 
10
 
11
- size_js="""
12
- function imgSize(){
13
- var myImg = document.getElementsByClassName("my_im");
14
- var realWidth = myImg.naturalWidth;
15
- var realHeight = myImg.naturalHeight;
16
- alert("Original width=" + realWidth + ", " + "Original height=" + realHeight);
17
- }"""
 
 
18
 
19
- def dl(inp):
20
- out = None
21
- out_file = []
22
- try:
23
- # Create a unique identifier for each download
24
- uid = uuid.uuid4()
25
- os.makedirs(f"{uid}", exist_ok=True) # Create a directory for storing the downloaded file
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
- # Generate a unique output filename
28
- inp_out = inp.replace("https://", "").replace("/", "_").replace(".", "_").replace("=", "_").replace("?", "_")
 
 
 
 
29
 
30
- # Download using yt_dlp
31
- command = [
32
- "yt-dlp",
33
- inp,
34
- "--trim-filenames", "160",
35
- "-o", f"{uid}/{inp_out}.mp4",
36
- "-S", "res,mp4",
37
- "--recode", "mp4"
38
- ]
39
 
40
- if "twitter" in inp:
41
- command.insert(2, "--extractor-arg")
42
- command.insert(3, "twitter:api=syndication")
 
43
 
44
- # Use subprocess to execute the command and handle errors
45
- result = subprocess.run(command, capture_output=True, text=True)
 
 
 
46
 
47
- # Check if the command was successful
48
- if result.returncode != 0:
49
- print("Error occurred:", result.stderr)
50
- return None, gr.HTML(f"<p>Error: {result.stderr}</p>"), "", ""
 
 
 
 
 
51
 
52
- out = f"{uid}/{inp_out}.mp4"
53
- print(out)
 
54
 
 
 
 
 
 
 
 
 
 
 
 
55
  except Exception as e:
56
- print(e)
57
- return None, gr.HTML(f"<p>Error: {str(e)}</p>"), "", ""
58
-
59
- return out, gr.HTML(""), "", ""
60
 
61
  def process_vid(file, cur_frame, every_n):
62
- new_video_in = str(file)
63
- capture = cv2.VideoCapture(new_video_in)
64
- frame_count = int(capture.get(cv2.CAP_PROP_FRAME_COUNT))
65
- rev_img_searcher = ReverseImageSearcher()
66
- html_out = ""
67
- count = int(every_n)
68
- if cur_frame == "" or cur_frame is None:
69
- start_frame = 0
70
- else:
71
- start_frame = int(cur_frame)
72
  try:
73
- for i in range(start_frame, frame_count - 1):
 
 
 
 
 
 
 
 
 
 
 
74
  if count == int(every_n):
75
  count = 1
76
- print(i)
77
  capture.set(cv2.CAP_PROP_POS_FRAMES, i)
78
- ret, frame_f = capture.read()
 
79
  if not ret:
80
  continue
81
- cv2.imwrite(f"{uid}-vid_tmp{i}.png", frame_f)
82
- out = os.path.abspath(f"{uid}-vid_tmp{i}.png")
83
- out_url = f'https://nymbo-reverse-image.hf.space/file={out}'
84
- print(out)
85
- res = rev_img_searcher.search(out_url)
86
- out_cnt = 0
87
- if len(res) > 0:
88
- for search_item in res:
89
- out_cnt += 1
90
- html_out = f"""{html_out}
91
- <div>
92
- Title: {search_item.page_title}<br>
93
- Site: <a href='{search_item.page_url}' target='_blank' rel='noopener noreferrer'>{search_item.page_url}</a><br>
94
- Img: <a href='{search_item.image_url}' target='_blank' rel='noopener noreferrer'>{search_item.image_url}</a><br>
95
- <img class='my_im' src='{search_item.image_url}'><br>
96
- </div>"""
97
- return gr.HTML(f'<h1>Total Found: {out_cnt}</h1><br>{html_out}'), f"Found frame: {i}", i + int(every_n)
98
  count += 1
99
- print(i + 1)
100
  except Exception as e:
101
- return gr.HTML(f"{e}"), "", ""
 
 
 
 
 
 
102
  return gr.HTML('No frame matches found.'), "", ""
103
 
104
  def process_im(file, url):
 
105
  if not url.startswith("https://nymbo"):
106
  return url
107
- else:
 
 
 
108
  read_file = Image.open(file)
109
- read_file.save(f"{uid}-tmp.png")
110
- action_input = f"{uid}-tmp.png"
111
- out = os.path.abspath(action_input)
112
- out_url = f'https://nymbo-reverse-image.hf.space/file={out}'
113
  return out_url
 
 
114
 
115
  def rev_im(image):
116
- out_list = []
117
- html_out = """"""
118
- image = cv2.imread(image)
119
- cv2.imwrite(f"{uid}-im_tmp.png", image)
120
- out = os.path.abspath(f"{uid}-im_tmp.png")
121
- out_url = f'https://nymbo-reverse-image.hf.space/file={out}'
122
- rev_img_searcher = ReverseImageSearcher()
123
- res = rev_img_searcher.search(out_url)
124
- count = 0
125
- for search_item in res:
126
- count += 1
127
- html_out = f"""{html_out}
128
- <div>
129
- Title: {search_item.page_title}<br>
130
- Site: <a href='{search_item.page_url}' target='_blank' rel='noopener noreferrer'>{search_item.page_url}</a><br>
131
- Img: <a href='{search_item.image_url}' target='_blank' rel='noopener noreferrer'>{search_item.image_url}</a><br>
132
- <img class='my_im' src='{search_item.image_url}'><br>
133
- </div>"""
134
- return gr.HTML(f'<h1>Total Found: {count}</h1><br>{html_out}')
135
-
136
- with gr.Blocks() as app:
137
- with gr.Row():
138
- gr.Column()
139
- with gr.Column():
140
- source_tog = gr.Radio(choices=["Image", "Video"], value="Image")
141
- with gr.Box(visible=True) as im_box:
142
- inp_url = gr.Textbox(label="Image URL")
143
- load_im_btn = gr.Button("Load Image")
144
- inp_im = gr.Image(label="Search Image", type='filepath')
145
- go_btn_im = gr.Button("Start Image Search")
146
- with gr.Box(visible=False) as vid_box:
147
- vid_url = gr.Textbox(label="Video URL")
148
- vid_url_btn = gr.Button("Load URL")
149
- inp_vid = gr.Video(label="Search Video")
150
- with gr.Row():
151
- every_n = gr.Number(label="Every /nth frame", value=10)
152
- stat_box = gr.Textbox(label="Status")
153
- with gr.Row():
154
- go_btn_vid = gr.Button("Start Video Search")
155
- next_btn = gr.Button("Next")
156
-
157
- with gr.Row():
158
- html_out = gr.HTML("""")
159
- with gr.Row(visible=False):
160
- hid_box = gr.Textbox()
161
-
162
- def shuf(tog):
163
- if tog == "Image":
164
- return gr.update(visible=True), gr.update(visible=False)
165
- if tog == "Video":
166
- return gr.update(visible=False), gr.update(visible=True)
167
 
168
- def load_image(url):
169
- return url
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
 
171
- im_load = load_im_btn.click(load_image, inp_url, inp_im)
172
- next_btn.click(process_vid, [inp_vid, hid_box, every_n], [html_out, stat_box, hid_box])
173
- vid_load = vid_url_btn.click(dl, vid_url, [inp_vid, html_out, stat_box, hid_box])
174
- vid_proc = go_btn_vid.click(process_vid, [inp_vid, hid_box, every_n], [html_out, stat_box, hid_box])
175
- im_proc = go_btn_im.click(rev_im, inp_im, [html_out])
176
- source_tog.change(shuf, [source_tog], [im_box, vid_box], cancels=[vid_proc, im_proc, im_load, vid_load])
177
 
178
- app.queue(concurrency_count=20).launch()
 
 
 
1
  import gradio as gr
2
+ import requests
3
  import yt_dlp
4
  import cv2
5
  from google_img_source_search import ReverseImageSearcher
6
  from PIL import Image
7
+ import os
8
  import uuid
9
+ from pathlib import Path
10
+ import shutil
11
+ import logging
12
+ import sys
13
 
14
+ # Set up logging
15
+ logging.basicConfig(
16
+ level=logging.INFO,
17
+ format='%(asctime)s - %(levelname)s - %(message)s',
18
+ handlers=[
19
+ logging.StreamHandler(sys.stdout)
20
+ ]
21
+ )
22
+ logger = logging.getLogger(__name__)
23
 
24
+ # Custom CSS for better presentation
25
+ CUSTOM_CSS = """
26
+ .result-item {
27
+ margin: 20px 0;
28
+ padding: 15px;
29
+ border: 1px solid #ddd;
30
+ border-radius: 5px;
31
+ }
32
+ .result-item img {
33
+ max-width: 100%;
34
+ height: auto;
35
+ margin: 10px 0;
36
+ }
37
+ .error-message {
38
+ color: red;
39
+ padding: 10px;
40
+ border: 1px solid red;
41
+ border-radius: 5px;
42
+ margin: 10px 0;
43
+ }
44
+ """
45
 
46
+ def create_temp_dir():
47
+ """Create a temporary directory with UUID."""
48
+ uid = str(uuid.uuid4())
49
+ temp_dir = Path(f"temp_{uid}")
50
+ temp_dir.mkdir(exist_ok=True)
51
+ return temp_dir
52
 
53
+ def cleanup_temp_dir(temp_dir):
54
+ """Safely clean up temporary directory and its contents."""
55
+ try:
56
+ if temp_dir.exists():
57
+ shutil.rmtree(str(temp_dir))
58
+ except Exception as e:
59
+ logger.error(f"Error cleaning up temporary directory: {e}")
 
 
60
 
61
+ def dl(inp):
62
+ """Download video from URL using yt-dlp."""
63
+ if not inp or not inp.strip():
64
+ return None, gr.HTML("<p class='error-message'>Please provide a valid URL</p>"), "", ""
65
 
66
+ temp_dir = create_temp_dir()
67
+ try:
68
+ # Sanitize input filename
69
+ inp_out = inp.replace("https://", "").replace("/", "_").replace(".", "_").replace("=", "_").replace("?", "_")
70
+ output_path = str(temp_dir / f"{inp_out}.mp4")
71
 
72
+ # Configure yt-dlp options
73
+ ydl_opts = {
74
+ 'format': 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/mp4',
75
+ 'outtmpl': output_path,
76
+ 'quiet': True,
77
+ 'no_warnings': True,
78
+ 'extract_flat': False,
79
+ 'merge_output_format': 'mp4'
80
+ }
81
 
82
+ # Handle Twitter URLs differently
83
+ if "twitter" in inp.lower():
84
+ ydl_opts['extractor_args'] = {'twitter': {'api': ['syndication']}}
85
 
86
+ # Download the video
87
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
88
+ logger.info(f"Downloading video from: {inp}")
89
+ ydl.download([inp])
90
+
91
+ if os.path.exists(output_path):
92
+ logger.info(f"Successfully downloaded to: {output_path}")
93
+ return output_path, gr.HTML(""), "", ""
94
+ else:
95
+ raise Exception("Download completed but file not found")
96
+
97
  except Exception as e:
98
+ logger.error(f"Error downloading video: {e}")
99
+ cleanup_temp_dir(temp_dir)
100
+ return None, gr.HTML(f"<p class='error-message'>Error: {str(e)}</p>"), "", ""
 
101
 
102
  def process_vid(file, cur_frame, every_n):
103
+ """Process video file and search for frames."""
104
+ if not file:
105
+ return gr.HTML("<p class='error-message'>No video file provided</p>"), "", ""
106
+
107
+ temp_dir = create_temp_dir()
108
+ capture = None
109
+
 
 
 
110
  try:
111
+ capture = cv2.VideoCapture(str(file))
112
+ if not capture.isOpened():
113
+ raise Exception("Failed to open video file")
114
+
115
+ frame_count = int(capture.get(cv2.CAP_PROP_FRAME_COUNT))
116
+ rev_img_searcher = ReverseImageSearcher()
117
+ html_out = ""
118
+ count = int(every_n)
119
+
120
+ start_frame = int(cur_frame) if cur_frame and cur_frame.strip() else 0
121
+
122
+ for i in range(start_frame, frame_count-1):
123
  if count == int(every_n):
124
  count = 1
125
+ logger.info(f"Processing frame {i}")
126
  capture.set(cv2.CAP_PROP_POS_FRAMES, i)
127
+ ret, frame = capture.read()
128
+
129
  if not ret:
130
  continue
131
+
132
+ temp_image = temp_dir / f"frame_{i}.png"
133
+ cv2.imwrite(str(temp_image), frame)
134
+
135
+ # Generate the URL for the temporary image
136
+ image_path = os.path.abspath(str(temp_image))
137
+ image_url = f'https://nymbo-reverse-image.hf.space/file={image_path}'
138
+
139
+ try:
140
+ results = rev_img_searcher.search(image_url)
141
+ if results:
142
+ out_cnt = len(results)
143
+ html_out = build_results_html(results)
144
+ return gr.HTML(f'<h1>Total Found: {out_cnt}</h1><br>{html_out}'), f"Found frame: {i}", i+int(every_n)
145
+ except Exception as search_error:
146
+ logger.error(f"Search error on frame {i}: {search_error}")
147
+
148
  count += 1
149
+
150
  except Exception as e:
151
+ logger.error(f"Error processing video: {e}")
152
+ return gr.HTML(f'<p class="error-message">Error: {str(e)}</p>'), "", ""
153
+ finally:
154
+ if capture is not None:
155
+ capture.release()
156
+ cleanup_temp_dir(temp_dir)
157
+
158
  return gr.HTML('No frame matches found.'), "", ""
159
 
160
  def process_im(file, url):
161
+ """Process image file or URL."""
162
  if not url.startswith("https://nymbo"):
163
  return url
164
+
165
+ temp_dir = create_temp_dir()
166
+ try:
167
+ temp_image = temp_dir / "temp.png"
168
  read_file = Image.open(file)
169
+ read_file.save(str(temp_image))
170
+
171
+ out_url = f'https://nymbo-reverse-image.hf.space/file={os.path.abspath(str(temp_image))}'
 
172
  return out_url
173
+ finally:
174
+ cleanup_temp_dir(temp_dir)
175
 
176
  def rev_im(image):
177
+ """Reverse image search for a single image."""
178
+ if not image:
179
+ return gr.HTML("<p class='error-message'>No image provided</p>")
180
+
181
+ temp_dir = create_temp_dir()
182
+ try:
183
+ temp_image = temp_dir / "temp.png"
184
+ image_cv = cv2.imread(image)
185
+ cv2.imwrite(str(temp_image), image_cv)
186
+
187
+ out_url = f'https://nymbo-reverse-image.hf.space/file={os.path.abspath(str(temp_image))}'
188
+
189
+ rev_img_searcher = ReverseImageSearcher()
190
+ results = rev_img_searcher.search(out_url)
191
+
192
+ return gr.HTML(build_results_html(results, len(results)))
193
+ except Exception as e:
194
+ logger.error(f"Error in reverse image search: {e}")
195
+ return gr.HTML(f'<p class="error-message">Error: {str(e)}</p>')
196
+ finally:
197
+ cleanup_temp_dir(temp_dir)
198
+
199
+ def build_results_html(results, count=None):
200
+ """Build HTML output for search results."""
201
+ html_out = f"<h1>Total Found: {count}</h1><br>" if count is not None else ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
 
203
+ for item in results:
204
+ html_out += f"""
205
+ <div class="result-item">
206
+ <h3>{item.page_title}</h3>
207
+ <p>Source: <a href='{item.page_url}' target='_blank' rel='noopener noreferrer'>{item.page_url}</a></p>
208
+ <p>Image: <a href='{item.image_url}' target='_blank' rel='noopener noreferrer'>{item.image_url}</a></p>
209
+ <img class='my_im' src='{item.image_url}' alt='{item.page_title}'>
210
+ </div>
211
+ """
212
+ return html_out
213
+
214
+ def create_ui():
215
+ """Create the Gradio interface."""
216
+ with gr.Blocks(css=CUSTOM_CSS) as app:
217
+ with gr.Row():
218
+ gr.Column()
219
+ with gr.Column():
220
+ source_tog = gr.Radio(
221
+ choices=["Image", "Video"],
222
+ value="Image",
223
+ label="Search Type"
224
+ )
225
+
226
+ # Image search interface
227
+ with gr.Box(visible=True) as im_box:
228
+ inp_url = gr.Textbox(label="Image URL")
229
+ load_im_btn = gr.Button("Load Image", variant="primary")
230
+ inp_im = gr.Image(label="Search Image", type='filepath')
231
+ go_btn_im = gr.Button("Search", variant="primary")
232
+
233
+ # Video search interface
234
+ with gr.Box(visible=False) as vid_box:
235
+ vid_url = gr.Textbox(label="Video URL")
236
+ vid_url_btn = gr.Button("Load URL", variant="primary")
237
+ inp_vid = gr.Video(label="Search Video")
238
+ with gr.Row():
239
+ every_n = gr.Number(
240
+ label="Every nth frame",
241
+ value=10,
242
+ minimum=1,
243
+ maximum=100,
244
+ step=1
245
+ )
246
+ stat_box = gr.Textbox(label="Status", interactive=False)
247
+ with gr.Row():
248
+ go_btn_vid = gr.Button("Start", variant="primary")
249
+ next_btn = gr.Button("Next Frame", variant="secondary")
250
+
251
+ gr.Column()
252
+
253
+ with gr.Row():
254
+ html_out = gr.HTML()
255
+
256
+ with gr.Row(visible=False):
257
+ hid_box = gr.Textbox()
258
+
259
+ # Event handlers
260
+ def toggle_interface(tog):
261
+ """Toggle between image and video interfaces."""
262
+ return (
263
+ gr.update(visible=tog == "Image"),
264
+ gr.update(visible=tog == "Video")
265
+ )
266
+
267
+ # Connect all event handlers
268
+ source_tog.change(
269
+ toggle_interface,
270
+ [source_tog],
271
+ [im_box, vid_box],
272
+ cancels=[go_btn_vid.click, go_btn_im.click, load_im_btn.click, vid_url_btn.click]
273
+ )
274
+
275
+ load_im_btn.click(lambda x: x, inp_url, inp_im)
276
+ next_btn.click(process_vid, [inp_vid, hid_box, every_n], [html_out, stat_box, hid_box])
277
+ vid_url_btn.click(dl, vid_url, [inp_vid, html_out, stat_box, hid_box])
278
+ go_btn_vid.click(process_vid, [inp_vid, hid_box, every_n], [html_out, stat_box, hid_box])
279
+ go_btn_im.click(rev_im, inp_im, html_out)
280
 
281
+ return app
 
 
 
 
 
282
 
283
+ if __name__ == "__main__":
284
+ app = create_ui()
285
+ app.queue(concurrency_count=20).launch()