DaniFera commited on
Commit
e5e7fa1
·
verified ·
1 Parent(s): 5f371f8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +76 -44
app.py CHANGED
@@ -1,4 +1,4 @@
1
- # Versión 3.2: App Pública con Logs ACTIVOS (Para verificar que funciona)
2
  import gradio as gr
3
  import pandas as pd
4
  import config
@@ -15,14 +15,11 @@ engine = PDFEngine()
15
 
16
  # --- SEGURIDAD: GARBAGE COLLECTOR ---
17
  # NOTA PARA DESARROLLADORES / DUPLICADORES DEL ESPACIO:
18
- # Esta función limpia los archivos cada minuto. Por defecto está en modo SILENCIOSO
19
- # para no ensuciar los logs en producción.
20
- # Si quieres ver qué está pasando, DESCOMENTA las líneas que empiezan por "#print"
21
- # y elimina los "pass" si es necesario.
22
  def cleanup_cron():
23
  """
24
  Revisa cada minuto. Borra archivos Y carpetas mayores a 5 minutos.
25
- Modo Producción: Logs desactivados por defecto.
26
  """
27
  print("[INIT] 🛡️ Sistema de seguridad y auto-borrado ACTIVO.")
28
 
@@ -33,7 +30,6 @@ def cleanup_cron():
33
  LIMIT_MINUTES = 5
34
  cutoff = time.time() - (LIMIT_MINUTES * 60)
35
 
36
- # Cabecera de ronda
37
  #print(f"--- [SEGURIDAD] Ronda de limpieza: {time.strftime('%H:%M:%S')} ---")
38
 
39
  if os.path.exists(config.TEMP_DIR):
@@ -49,48 +45,41 @@ def cleanup_cron():
49
  for filename in items:
50
  filepath = os.path.join(config.TEMP_DIR, filename)
51
 
52
- # Ofuscación visual
53
  if len(filename) > 8:
54
  masked = f"{filename[:4]}****{os.path.splitext(filename)[1]}"
55
  else:
56
  masked = filename
57
 
58
- # Calcular edad
59
  try:
60
  file_time = os.path.getmtime(filepath)
61
  age_sec = time.time() - file_time
62
- age_str = f"{int(age_sec // 60)}m {int(age_sec % 60)}s"
63
-
64
  is_expired = file_time < cutoff
65
  except FileNotFoundError:
66
  continue
67
 
68
- # --- CASO 1: ES UN ARCHIVO ---
69
  if os.path.isfile(filepath):
70
  if is_expired:
71
  try:
72
  os.remove(filepath)
73
- #print(f"❌ [BORRADO] Archivo {masked} | Edad: {age_str}")
74
  except Exception as e:
75
- #print(f"⚠️ [ERROR] No se pudo borrar archivo {masked}: {e}")
76
  pass
77
  else:
78
- #print(f"✅ [VIGENTE] Archivo {masked} | Edad: {age_str}")
79
  pass
80
 
81
- # --- CASO 2: ES UNA CARPETA ---
82
  elif os.path.isdir(filepath):
83
  if is_expired:
84
  try:
85
  shutil.rmtree(filepath)
86
- #print(f"🗑️ [LIMPIEZA] Carpeta {masked} eliminada | Edad: {age_str}")
87
  except Exception as e:
88
- #print(f"⚠️ [ERROR] No se pudo borrar carpeta {masked}: {e}")
89
  pass
90
  else:
91
- #print(f"📂 [VIGENTE] Carpeta {masked} | Edad: {age_str}")
92
  pass
93
-
94
  else:
95
  #print("[INFO] Carpeta temporal aún no creada.")
96
  pass
@@ -98,20 +87,35 @@ def cleanup_cron():
98
  except Exception as e:
99
  print(f"[CRITICAL] Error en limpieza: {e}")
100
 
101
- # Iniciar hilo en segundo plano
102
  threading.Thread(target=cleanup_cron, daemon=True).start()
103
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
  # --- WRAPPERS ---
105
  def update_file_list(files):
106
  if not files: return pd.DataFrame(), ""
107
  data = [[i, f.split("/")[-1]] for i, f in enumerate(files)]
108
  return pd.DataFrame(data, columns=["ID", "Archivo"]), ",".join([str(i) for i in range(len(files))])
109
 
110
- def process_merge(files, order_str):
 
111
  if not files: return None
112
  try:
113
  indices = [int(x.strip()) for x in order_str.split(",") if x.strip().isdigit()]
114
- return engine.merge_pdfs(files, indices)
115
  except Exception as e: raise gr.Error(str(e))
116
 
117
  def load_info(f):
@@ -179,6 +183,17 @@ def process_text(f):
179
  try: return engine.extract_text(f)
180
  except Exception as e: raise gr.Error(str(e))
181
 
 
 
 
 
 
 
 
 
 
 
 
182
  # WRAPPERS OFFICE
183
  def process_word(f):
184
  if not f: return None
@@ -207,44 +222,40 @@ with gr.Blocks(title=config.APP_TITLE, theme=gr.themes.Soft()) as demo:
207
 
208
  gr.Markdown(f"# {config.APP_TITLE}")
209
  gr.Markdown("""
 
210
  Los archivos se procesan en memoria y se **autodestruyen tras 5 minutos**.
211
  """)
212
 
213
- gr.HTML("""
214
- <div style="display: flex; gap: 10px; margin-bottom: 20px;">
215
- <span>⚡ ¿Funciona lento o quieres seguridad extra? Duplica este espacio para tener tu propia instancia privada.</span>
216
- </div>
217
- """)
218
 
219
  with gr.Tabs():
220
-
221
- # 1. UNIR
222
  with gr.TabItem("Unir"):
223
  with gr.Row():
224
  with gr.Column(scale=1):
225
  m_files = gr.File(file_count="multiple", label="Archivos", file_types=[".pdf"])
226
  with gr.Column(scale=2):
227
  m_tbl = gr.Dataframe(headers=["ID", "Archivo"], interactive=False)
228
- m_ord = gr.Textbox(label="Orden de los documentos según ID", placeholder="Ej: 0, 2, 1")
229
- m_btn = gr.Button("Unir", variant="primary")
 
 
230
  m_out = gr.File(label="Resultado")
231
  m_files.change(update_file_list, m_files, [m_tbl, m_ord])
232
- m_btn.click(process_merge, [m_files, m_ord], m_out)
 
233
 
234
  # 2. DIVIDIR / REORDENAR
235
  with gr.TabItem("Dividir / Reordenar"):
236
-
237
- # BLOQUE SUPERIOR
238
  dr_f = gr.File(label="PDF Origen", file_types=[".pdf"])
239
  dr_inf = gr.Markdown("")
240
  dr_pg = gr.State(0)
241
-
242
- # BLOQUE INFERIOR
243
  with gr.Tabs():
244
  with gr.Tab("Extraer"):
 
245
  with gr.Row():
246
  with gr.Column():
247
- s_rng = gr.Textbox(label="Rangos de división", placeholder="Ej: 1-3, 5")
248
  with gr.Row():
249
  s_prv = gr.Button("Preview")
250
  s_btn = gr.Button("Dividir (ZIP)", variant="primary")
@@ -253,7 +264,6 @@ with gr.Blocks(title=config.APP_TITLE, theme=gr.themes.Soft()) as demo:
253
  s_out = gr.File(label="ZIP")
254
  s_prv.click(update_split_preview, [dr_f, s_rng, dr_pg], s_gal)
255
  s_btn.click(process_split, [dr_f, s_rng], s_out)
256
-
257
  with gr.Tab("Reordenar"):
258
  gr.Markdown("Crea un PDF con nuevo orden.")
259
  with gr.Row():
@@ -263,7 +273,6 @@ with gr.Blocks(title=config.APP_TITLE, theme=gr.themes.Soft()) as demo:
263
  with gr.Column():
264
  r_out = gr.File(label="PDF Reordenado")
265
  r_btn.click(process_reorder, [dr_f, r_ord], r_out)
266
-
267
  dr_f.change(load_info, dr_f, [dr_inf, dr_pg, s_out])
268
 
269
  # 3. COMPRIMIR
@@ -272,7 +281,7 @@ with gr.Blocks(title=config.APP_TITLE, theme=gr.themes.Soft()) as demo:
272
  with gr.Column():
273
  c_f = gr.File(label="PDF Original", file_types=[".pdf"])
274
  c_l = gr.Radio(["Baja (Máxima calidad)", "Media (Recomendado - eBook)", "Alta (Pantalla - 72dpi)"], label="Nivel", value="Media (Recomendado - eBook)")
275
- c_b = gr.Button("Comprimir Documento", variant="primary")
276
  with gr.Column():
277
  c_out = gr.File(label="PDF Comprimido")
278
  c_b.click(process_compress, [c_f, c_l], c_out)
@@ -324,8 +333,8 @@ with gr.Blocks(title=config.APP_TITLE, theme=gr.themes.Soft()) as demo:
324
  co = gr.File(label="Informe PDF")
325
  cb_btn.click(process_compare, [ca, cb], co)
326
 
327
- # 6. EXTRAS
328
- with gr.TabItem("Extras (Rotar,Proteger,Metadatos)"):
329
  with gr.Tab("Rotar"):
330
  with gr.Row():
331
  with gr.Column():
@@ -338,6 +347,29 @@ with gr.Blocks(title=config.APP_TITLE, theme=gr.themes.Soft()) as demo:
338
  rf.change(update_rot_preview, [rf, ra], rp)
339
  ra.change(update_rot_preview, [rf, ra], rp)
340
  rb.click(process_rotate, [rf, ra], ro)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
341
 
342
  with gr.Tab("Proteger"):
343
  with gr.Row():
@@ -348,7 +380,7 @@ with gr.Blocks(title=config.APP_TITLE, theme=gr.themes.Soft()) as demo:
348
  with gr.Column():
349
  po = gr.File(label="Protegido")
350
  pb.click(process_protect, [pf, pp], po)
351
-
352
  with gr.Tab("Info/Texto"):
353
  with gr.Row():
354
  with gr.Column():
 
1
+ # Versión 3.6: App Pública Completa (Watermark, Reparar, Numeración)
2
  import gradio as gr
3
  import pandas as pd
4
  import config
 
15
 
16
  # --- SEGURIDAD: GARBAGE COLLECTOR ---
17
  # NOTA PARA DESARROLLADORES / DUPLICADORES DEL ESPACIO:
18
+ # Esta función limpia los archivos cada minuto. Por defecto está en modo SILENCIOSO.
19
+ # Si quieres ver qué está pasando, DESCOMENTA las líneas que empiezan por "#print".
 
 
20
  def cleanup_cron():
21
  """
22
  Revisa cada minuto. Borra archivos Y carpetas mayores a 5 minutos.
 
23
  """
24
  print("[INIT] 🛡️ Sistema de seguridad y auto-borrado ACTIVO.")
25
 
 
30
  LIMIT_MINUTES = 5
31
  cutoff = time.time() - (LIMIT_MINUTES * 60)
32
 
 
33
  #print(f"--- [SEGURIDAD] Ronda de limpieza: {time.strftime('%H:%M:%S')} ---")
34
 
35
  if os.path.exists(config.TEMP_DIR):
 
45
  for filename in items:
46
  filepath = os.path.join(config.TEMP_DIR, filename)
47
 
 
48
  if len(filename) > 8:
49
  masked = f"{filename[:4]}****{os.path.splitext(filename)[1]}"
50
  else:
51
  masked = filename
52
 
 
53
  try:
54
  file_time = os.path.getmtime(filepath)
55
  age_sec = time.time() - file_time
 
 
56
  is_expired = file_time < cutoff
57
  except FileNotFoundError:
58
  continue
59
 
60
+ # CASO 1: ARCHIVO
61
  if os.path.isfile(filepath):
62
  if is_expired:
63
  try:
64
  os.remove(filepath)
65
+ #print(f"❌ [BORRADO] Archivo {masked}")
66
  except Exception as e:
 
67
  pass
68
  else:
69
+ #print(f"✅ [VIGENTE] Archivo {masked}")
70
  pass
71
 
72
+ # CASO 2: CARPETA
73
  elif os.path.isdir(filepath):
74
  if is_expired:
75
  try:
76
  shutil.rmtree(filepath)
77
+ #print(f"🗑️ [LIMPIEZA] Carpeta {masked} eliminada")
78
  except Exception as e:
 
79
  pass
80
  else:
81
+ #print(f"📂 [VIGENTE] Carpeta {masked}")
82
  pass
 
83
  else:
84
  #print("[INFO] Carpeta temporal aún no creada.")
85
  pass
 
87
  except Exception as e:
88
  print(f"[CRITICAL] Error en limpieza: {e}")
89
 
 
90
  threading.Thread(target=cleanup_cron, daemon=True).start()
91
 
92
+ # --- GENERADOR DE ENLACE ---
93
+ def get_duplicate_html():
94
+ space_id = os.environ.get('SPACE_ID', None)
95
+ url = f"https://huggingface.co/spaces/{space_id}?duplicate=true" if space_id else "https://huggingface.co/spaces?duplicate=true"
96
+ return f"""
97
+ <div style="display: flex; align-items: center; gap: 10px; margin-bottom: 20px; background-color: #f9fafb; padding: 10px; border-radius: 8px; border: 1px solid #e5e7eb;">
98
+ <a href="{url}" target="_blank">
99
+ <img src="https://huggingface.co/datasets/huggingface/badges/raw/main/duplicate-this-space-sm.svg" alt="Duplicate Space" style="height: 30px;">
100
+ </a>
101
+ <span style="font-size: 0.9em; color: #4b5563;">
102
+ ⚡ <b>¿Va lento?</b> Haz clic para duplicar este espacio y tener tu propia instancia privada (y activar logs si quieres).
103
+ </span>
104
+ </div>
105
+ """
106
+
107
  # --- WRAPPERS ---
108
  def update_file_list(files):
109
  if not files: return pd.DataFrame(), ""
110
  data = [[i, f.split("/")[-1]] for i, f in enumerate(files)]
111
  return pd.DataFrame(data, columns=["ID", "Archivo"]), ",".join([str(i) for i in range(len(files))])
112
 
113
+ # MODIFICADO: Acepta use_numbering
114
+ def process_merge(files, order_str, use_numbering):
115
  if not files: return None
116
  try:
117
  indices = [int(x.strip()) for x in order_str.split(",") if x.strip().isdigit()]
118
+ return engine.merge_pdfs(files, indices, use_numbering)
119
  except Exception as e: raise gr.Error(str(e))
120
 
121
  def load_info(f):
 
183
  try: return engine.extract_text(f)
184
  except Exception as e: raise gr.Error(str(e))
185
 
186
+ # NUEVOS WRAPPERS
187
+ def process_watermark(f, t):
188
+ if not f or not t: return None
189
+ try: return engine.add_watermark(f, t)
190
+ except Exception as e: raise gr.Error(str(e))
191
+
192
+ def process_repair(f):
193
+ if not f: return None
194
+ try: return engine.repair_pdf(f)
195
+ except Exception as e: raise gr.Error(str(e))
196
+
197
  # WRAPPERS OFFICE
198
  def process_word(f):
199
  if not f: return None
 
222
 
223
  gr.Markdown(f"# {config.APP_TITLE}")
224
  gr.Markdown("""
225
+ ### 🛡️ Suite PDF: Libre, Gratuita y Privada
226
  Los archivos se procesan en memoria y se **autodestruyen tras 5 minutos**.
227
  """)
228
 
229
+ gr.HTML(get_duplicate_html())
 
 
 
 
230
 
231
  with gr.Tabs():
232
+ # 1. UNIR (Actualizado con Numeración)
 
233
  with gr.TabItem("Unir"):
234
  with gr.Row():
235
  with gr.Column(scale=1):
236
  m_files = gr.File(file_count="multiple", label="Archivos", file_types=[".pdf"])
237
  with gr.Column(scale=2):
238
  m_tbl = gr.Dataframe(headers=["ID", "Archivo"], interactive=False)
239
+ m_ord = gr.Textbox(label="Orden", placeholder="Ej: 0, 2, 1")
240
+ # NUEVO CHECKBOX
241
+ m_nums = gr.Checkbox(label="Numerar páginas (1 de X)", value=False)
242
+ m_btn = gr.Button("Unir PDF", variant="primary")
243
  m_out = gr.File(label="Resultado")
244
  m_files.change(update_file_list, m_files, [m_tbl, m_ord])
245
+ # Se pasa el valor del checkbox a la funcion
246
+ m_btn.click(process_merge, [m_files, m_ord, m_nums], m_out)
247
 
248
  # 2. DIVIDIR / REORDENAR
249
  with gr.TabItem("Dividir / Reordenar"):
 
 
250
  dr_f = gr.File(label="PDF Origen", file_types=[".pdf"])
251
  dr_inf = gr.Markdown("")
252
  dr_pg = gr.State(0)
 
 
253
  with gr.Tabs():
254
  with gr.Tab("Extraer"):
255
+ gr.Markdown("Separa páginas en un ZIP.")
256
  with gr.Row():
257
  with gr.Column():
258
+ s_rng = gr.Textbox(label="Rango", placeholder="Ej: 1-3, 5")
259
  with gr.Row():
260
  s_prv = gr.Button("Preview")
261
  s_btn = gr.Button("Dividir (ZIP)", variant="primary")
 
264
  s_out = gr.File(label="ZIP")
265
  s_prv.click(update_split_preview, [dr_f, s_rng, dr_pg], s_gal)
266
  s_btn.click(process_split, [dr_f, s_rng], s_out)
 
267
  with gr.Tab("Reordenar"):
268
  gr.Markdown("Crea un PDF con nuevo orden.")
269
  with gr.Row():
 
273
  with gr.Column():
274
  r_out = gr.File(label="PDF Reordenado")
275
  r_btn.click(process_reorder, [dr_f, r_ord], r_out)
 
276
  dr_f.change(load_info, dr_f, [dr_inf, dr_pg, s_out])
277
 
278
  # 3. COMPRIMIR
 
281
  with gr.Column():
282
  c_f = gr.File(label="PDF Original", file_types=[".pdf"])
283
  c_l = gr.Radio(["Baja (Máxima calidad)", "Media (Recomendado - eBook)", "Alta (Pantalla - 72dpi)"], label="Nivel", value="Media (Recomendado - eBook)")
284
+ c_b = gr.Button("Comprimir", variant="primary")
285
  with gr.Column():
286
  c_out = gr.File(label="PDF Comprimido")
287
  c_b.click(process_compress, [c_f, c_l], c_out)
 
333
  co = gr.File(label="Informe PDF")
334
  cb_btn.click(process_compare, [ca, cb], co)
335
 
336
+ # 6. EXTRAS (Actualizado con Marca de Agua y Reparar)
337
+ with gr.TabItem("Extras"):
338
  with gr.Tab("Rotar"):
339
  with gr.Row():
340
  with gr.Column():
 
347
  rf.change(update_rot_preview, [rf, ra], rp)
348
  ra.change(update_rot_preview, [rf, ra], rp)
349
  rb.click(process_rotate, [rf, ra], ro)
350
+
351
+ # NUEVO: Marca de Agua
352
+ with gr.Tab("Marca de Agua"):
353
+ gr.Markdown("Añade una marca de agua diagonal en todas las páginas.")
354
+ with gr.Row():
355
+ with gr.Column():
356
+ wf = gr.File(label="PDF")
357
+ wt = gr.Textbox(label="Texto Marca de Agua", placeholder="Ej: CONFIDENCIAL")
358
+ wb = gr.Button("Estampar", variant="primary")
359
+ with gr.Column():
360
+ wo = gr.File(label="PDF Marcado")
361
+ wb.click(process_watermark, [wf, wt], wo)
362
+
363
+ # NUEVO: Reparar
364
+ with gr.Tab("Reparar"):
365
+ gr.Markdown("Intenta arreglar PDFs corruptos o dañados reescribiéndolos con Ghostscript.")
366
+ with gr.Row():
367
+ with gr.Column():
368
+ repf = gr.File(label="PDF Dañado")
369
+ repb = gr.Button("Reparar", variant="primary")
370
+ with gr.Column():
371
+ repo = gr.File(label="PDF Reparado")
372
+ repb.click(process_repair, repf, repo)
373
 
374
  with gr.Tab("Proteger"):
375
  with gr.Row():
 
380
  with gr.Column():
381
  po = gr.File(label="Protegido")
382
  pb.click(process_protect, [pf, pp], po)
383
+
384
  with gr.Tab("Info/Texto"):
385
  with gr.Row():
386
  with gr.Column():