Scrappy-Doo commited on
Commit
9cc4e9e
·
verified ·
1 Parent(s): 2340d6b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +70 -90
app.py CHANGED
@@ -5,6 +5,7 @@ from urllib3.util.retry import Retry
5
  from bs4 import BeautifulSoup
6
  from urllib.parse import urljoin, urlparse
7
  import os
 
8
 
9
 
10
  def build_session():
@@ -30,9 +31,8 @@ def extract_media(url):
30
  "Accept-Encoding": "gzip, deflate, br",
31
  "Connection": "keep-alive",
32
  "Upgrade-Insecure-Requests": "1",
 
33
  }
34
- # Timeout separado: (conexión, lectura). Antes era un único valor de 50s,
35
- # lo que dejaba la UI colgada mucho tiempo si el sitio nunca respondía.
36
  response = session.get(url, headers=headers, timeout=(10, 20))
37
  response.raise_for_status()
38
 
@@ -42,32 +42,20 @@ def extract_media(url):
42
  images = []
43
  seen = set()
44
 
45
- # Buscar imágenes en <img>
46
  for img in soup.find_all("img"):
47
  src = img.get("src") or img.get("data-src") or img.get("data-original")
48
  if not src:
49
  continue
50
-
51
- # Convertir URLs relativas a absolutas
52
  img_url = urljoin(base_url, src)
53
-
54
- # Filtrar duplicados y URLs inválidas
55
  if img_url in seen or not img_url.startswith(("http://", "https://")):
56
  continue
57
  seen.add(img_url)
58
-
59
- # Obtener nombre del archivo
60
  parsed = urlparse(img_url)
61
  filename = os.path.basename(parsed.path) or "imagen.jpg"
62
  if not any(filename.lower().endswith(ext) for ext in [".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg", ".bmp"]):
63
  filename += ".jpg"
 
64
 
65
- images.append({
66
- "image": img_url,
67
- "caption": filename
68
- })
69
-
70
- # Buscar imágenes de fondo en <div> o elementos con style
71
  for tag in soup.find_all(style=True):
72
  style = tag["style"]
73
  if "url(" in style:
@@ -79,13 +67,9 @@ def extract_media(url):
79
  seen.add(bg_url)
80
  images.append({"image": bg_url, "caption": "background.jpg"})
81
 
82
- # =======================================================
83
- # NUEVA SECCIÓN: Buscar videos
84
- # =======================================================
85
  videos = []
86
  video_exts = [".mp4", ".webm", ".avi", ".mov", ".mkv", ".m4v", ".ogg"]
87
-
88
- # 1. Buscar en etiquetas <video> y <source>
89
  for tag in soup.find_all(["video", "source"]):
90
  src = tag.get("src") or tag.get("data-src")
91
  if not src:
@@ -98,8 +82,7 @@ def extract_media(url):
98
  if not any(filename.lower().endswith(ext) for ext in video_exts):
99
  filename += ".mp4"
100
  videos.append({"url": vid_url, "filename": filename})
101
-
102
- # 2. Buscar enlaces directos a videos en etiquetas <a>
103
  for a in soup.find_all("a", href=True):
104
  href = a["href"]
105
  if any(href.lower().endswith(ext) for ext in video_exts):
@@ -110,61 +93,55 @@ def extract_media(url):
110
  filename = os.path.basename(parsed.path) or "video.mp4"
111
  videos.append({"url": vid_url, "filename": filename})
112
 
113
- # Generar HTML para mostrar los enlaces de descarga de video
114
- if videos:
115
- video_html_content = "<ul style='font-size: 16px; line-height: 1.8;'>"
116
- for vid in videos:
117
- video_html_content += f'<li>🎬 <a href="{vid["url"]}" target="_blank" download="{vid["filename"]}" style="color: #007bff; text-decoration: none; font-weight: bold;">⬇️ Descargar {vid["filename"]}</a></li>'
118
- video_html_content += "</ul>"
119
- else:
120
- video_html_content = "<p style='color: gray; font-style: italic;'>No se encontraron videos directos en el HTML de esta página. (Nota: Sitios como YouTube usan transmisión fragmentada y no exponen un enlace .mp4 directo).</p>"
121
-
122
- if not images and not videos:
123
- return [], video_html_content, "No se encontraron imágenes ni videos en esta página."
124
-
125
- status_msg = f"Se encontraron {len(images)} imágenes y {len(videos)} videos."
126
- return [img["image"] for img in images], video_html_content, status_msg
127
-
128
- except requests.exceptions.Timeout:
129
- return [], "", (
130
- "El sitio no respondió a tiempo tras reintentar. Suele pasar cuando "
131
- "el dominio está filtrado a nivel de red/DNS (algunos hosts de archivos "
132
- "terminan en listas de bloqueo por abuso) o el servidor está caído/lento. "
133
- "Prueba el mismo enlace en el navegador desde esta misma red para confirmar."
134
- )
135
- except requests.exceptions.ConnectionError as e:
136
- # requests envuelve un ReadTimeout que agotó los reintentos como
137
- # ConnectionError (no como Timeout) — es un detalle de la librería,
138
- # no un tipo de fallo distinto al de arriba.
139
- if "ReadTimeoutError" in str(e) or "Read timed out" in str(e):
140
- return [], "", (
141
- "El sitio acepta la conexión pero nunca termina de responder, "
142
- "ni siquiera tras reintentar. Es el patrón de un bloqueo de "
143
- "red/firewall o de un mecanismo anti-bots que corta la respuesta "
144
- "a mitad de camino, no el de un timeout demasiado corto."
145
- )
146
- return [], "", f"No se pudo conectar con el sitio: {str(e)}"
147
- except requests.exceptions.RequestException as e:
148
- return [], "", f"Error al acceder a la URL: {str(e)}"
149
  except Exception as e:
150
- return [], "", f"Error inesperado: {str(e)}"
151
  finally:
152
  session.close()
153
 
154
 
155
- # Interfaz de Gradio
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  with gr.Blocks(title="Extractor de Imágenes y Videos Web") as demo:
157
- gr.Markdown("""
158
- # 🖼️ 🎬 Extractor de Imágenes y Videos
159
- Pega el enlace de cualquier página web y extrae todas las imágenes y videos disponibles directamente del HTML.
160
- """)
161
 
162
  with gr.Row():
163
- url_input = gr.Textbox(
164
- label="URL de la página",
165
- placeholder="https://ejemplo.com",
166
- scale=4
167
- )
168
  submit_btn = gr.Button("🔍 Extraer contenido", scale=1, variant="primary")
169
 
170
  status_text = gr.Textbox(label="Estado", interactive=False)
@@ -173,7 +150,6 @@ with gr.Blocks(title="Extractor de Imágenes y Videos Web") as demo:
173
  with gr.Column(scale=2):
174
  gallery = gr.Gallery(
175
  label="Imágenes encontradas",
176
- show_label=True,
177
  columns=4,
178
  rows=4,
179
  height="auto",
@@ -181,32 +157,36 @@ with gr.Blocks(title="Extractor de Imágenes y Videos Web") as demo:
181
  allow_preview=True
182
  )
183
  with gr.Column(scale=1):
184
- video_output = gr.HTML(
185
- label="Videos encontrados (Descarga directa)",
186
- value="<p>Los enlaces a videos aparecerán aquí.</p>"
187
- )
188
 
189
- # Información de descarga
190
- gr.Markdown("""
191
- ### 💡 Para descargar:
192
- - **Imágenes:** Haz **clic en cualquier imagen** para previsualizarla. Luego clic derecho → Guardar imagen como...
193
- - **Videos:** Haz clic en los enlaces de la columna derecha para descargarlos directamente a tu dispositivo.
194
- """)
 
195
 
196
  submit_btn.click(
197
- fn=extract_media,
198
  inputs=url_input,
199
- outputs=[gallery, video_output, status_text]
200
  )
201
 
202
- # Ejemplos (Añadida una página de ejemplo con video para que puedas probarlo al instante)
203
- gr.Examples(
204
- examples=[
205
- "https://es.wikipedia.org/wiki/Gato",
206
- "https://samplelib.com/sample-mp4.html"
207
- ],
208
- inputs=url_input,
209
- label="Ejemplos"
 
 
 
 
210
  )
211
 
212
  if __name__ == "__main__":
 
5
  from bs4 import BeautifulSoup
6
  from urllib.parse import urljoin, urlparse
7
  import os
8
+ import tempfile
9
 
10
 
11
  def build_session():
 
31
  "Accept-Encoding": "gzip, deflate, br",
32
  "Connection": "keep-alive",
33
  "Upgrade-Insecure-Requests": "1",
34
+ "Referer": "https://www.erome.com/",
35
  }
 
 
36
  response = session.get(url, headers=headers, timeout=(10, 20))
37
  response.raise_for_status()
38
 
 
42
  images = []
43
  seen = set()
44
 
 
45
  for img in soup.find_all("img"):
46
  src = img.get("src") or img.get("data-src") or img.get("data-original")
47
  if not src:
48
  continue
 
 
49
  img_url = urljoin(base_url, src)
 
 
50
  if img_url in seen or not img_url.startswith(("http://", "https://")):
51
  continue
52
  seen.add(img_url)
 
 
53
  parsed = urlparse(img_url)
54
  filename = os.path.basename(parsed.path) or "imagen.jpg"
55
  if not any(filename.lower().endswith(ext) for ext in [".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg", ".bmp"]):
56
  filename += ".jpg"
57
+ images.append({"image": img_url, "caption": filename})
58
 
 
 
 
 
 
 
59
  for tag in soup.find_all(style=True):
60
  style = tag["style"]
61
  if "url(" in style:
 
67
  seen.add(bg_url)
68
  images.append({"image": bg_url, "caption": "background.jpg"})
69
 
 
 
 
70
  videos = []
71
  video_exts = [".mp4", ".webm", ".avi", ".mov", ".mkv", ".m4v", ".ogg"]
72
+
 
73
  for tag in soup.find_all(["video", "source"]):
74
  src = tag.get("src") or tag.get("data-src")
75
  if not src:
 
82
  if not any(filename.lower().endswith(ext) for ext in video_exts):
83
  filename += ".mp4"
84
  videos.append({"url": vid_url, "filename": filename})
85
+
 
86
  for a in soup.find_all("a", href=True):
87
  href = a["href"]
88
  if any(href.lower().endswith(ext) for ext in video_exts):
 
93
  filename = os.path.basename(parsed.path) or "video.mp4"
94
  videos.append({"url": vid_url, "filename": filename})
95
 
96
+ # Guardamos la lista de videos para poder descargarlos después
97
+ return [img["image"] for img in images], videos, f"Se encontraron {len(images)} imágenes y {len(videos)} videos."
98
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  except Exception as e:
100
+ return [], [], f"Error: {str(e)}"
101
  finally:
102
  session.close()
103
 
104
 
105
+ def download_video(video_url, filename):
106
+ """Descarga el video con headers correctos y lo entrega al usuario"""
107
+ if not video_url:
108
+ return None
109
+
110
+ session = build_session()
111
+ headers = {
112
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
113
+ "Referer": "https://www.erome.com/",
114
+ "Accept": "*/*",
115
+ "Accept-Language": "es-ES,es;q=0.9",
116
+ "Connection": "keep-alive",
117
+ }
118
+
119
+ try:
120
+ with session.get(video_url, headers=headers, stream=True, timeout=(15, 60)) as r:
121
+ r.raise_for_status()
122
+
123
+ # Guardamos temporalmente el archivo
124
+ tmp = tempfile.NamedTemporaryFile(delete=False, suffix=os.path.splitext(filename)[1] or ".mp4")
125
+ for chunk in r.iter_content(chunk_size=8192):
126
+ if chunk:
127
+ tmp.write(chunk)
128
+ tmp.close()
129
+
130
+ return tmp.name # Gradio lo convertirá en archivo descargable
131
+
132
+ except Exception as e:
133
+ print(f"Error descargando video: {e}")
134
+ return None
135
+ finally:
136
+ session.close()
137
+
138
+
139
+ # Interfaz
140
  with gr.Blocks(title="Extractor de Imágenes y Videos Web") as demo:
141
+ gr.Markdown("# 🖼️ 🎬 Extractor de Imágenes y Videos")
 
 
 
142
 
143
  with gr.Row():
144
+ url_input = gr.Textbox(label="URL de la página", placeholder="https://ejemplo.com", scale=4)
 
 
 
 
145
  submit_btn = gr.Button("🔍 Extraer contenido", scale=1, variant="primary")
146
 
147
  status_text = gr.Textbox(label="Estado", interactive=False)
 
150
  with gr.Column(scale=2):
151
  gallery = gr.Gallery(
152
  label="Imágenes encontradas",
 
153
  columns=4,
154
  rows=4,
155
  height="auto",
 
157
  allow_preview=True
158
  )
159
  with gr.Column(scale=1):
160
+ video_dropdown = gr.Dropdown(label="Videos encontrados", choices=[], interactive=True)
161
+ download_btn = gr.Button("⬇️ Descargar video seleccionado", variant="primary")
162
+ video_file = gr.File(label="Archivo descargado")
 
163
 
164
+ # Estado oculto para guardar la lista de videos
165
+ videos_state = gr.State([])
166
+
167
+ def process(url):
168
+ images, videos, status = extract_media(url)
169
+ choices = [f"{v['filename']} | {v['url']}" for v in videos]
170
+ return images, gr.update(choices=choices, value=choices[0] if choices else None), videos, status
171
 
172
  submit_btn.click(
173
+ fn=process,
174
  inputs=url_input,
175
+ outputs=[gallery, video_dropdown, videos_state, status_text]
176
  )
177
 
178
+ def handle_download(selected, videos_list):
179
+ if not selected or not videos_list:
180
+ return None
181
+ # Extraemos la URL del texto del dropdown
182
+ url = selected.split(" | ")[-1]
183
+ filename = selected.split(" | ")[0]
184
+ return download_video(url, filename)
185
+
186
+ download_btn.click(
187
+ fn=handle_download,
188
+ inputs=[video_dropdown, videos_state],
189
+ outputs=video_file
190
  )
191
 
192
  if __name__ == "__main__":