DaniFera commited on
Commit
6f47e9b
·
verified ·
1 Parent(s): ef5f6c7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +116 -69
app.py CHANGED
@@ -1,4 +1,4 @@
1
- # Versión 1.6: Corrección de Rotación y UX mejorada
2
  import gradio as gr
3
  import pandas as pd
4
  import config
@@ -6,27 +6,26 @@ from core import PDFEngine
6
 
7
  engine = PDFEngine()
8
 
9
- # --- FUNCIONES AUXILIARES (Existentes) ---
10
- # (Mantenemos update_file_list, process_merge_ordered, load_pdf_info, etc. sin cambios)
11
- def update_file_list(files):
12
  if not files: return pd.DataFrame(), ""
13
  data = [[i, f.split("/")[-1]] for i, f in enumerate(files)]
14
  default_order = ",".join([str(i) for i in range(len(files))])
15
  return pd.DataFrame(data, columns=["ID", "Archivo"]), default_order
16
 
17
- def process_merge_ordered(files, order_str):
18
  if not files: return None
19
  try:
20
  indices = [int(x.strip()) for x in order_str.split(",") if x.strip().isdigit()]
21
  return engine.merge_pdfs(files, indices)
22
  except Exception as e: raise gr.Error(str(e))
23
 
24
- def load_pdf_info(file):
25
  if not file: return None, 0, gr.update(visible=False)
26
  info = engine.get_pdf_info(file)
27
  return f"Cargado: {info['name']} ({info['pages']} págs).", info['pages'], gr.update(visible=True)
28
 
29
- def update_preview_split(file, range_str, total_pages):
30
  if not file or not range_str: return None
31
  key_pages = engine.get_preview_indices_from_string(range_str, total_pages)
32
  if not key_pages: return None
@@ -36,60 +35,81 @@ def update_preview_split(file, range_str, total_pages):
36
  if path: gallery.append((path, f"Pág {p}"))
37
  return gallery
38
 
39
- def process_split_range(file, range_str):
40
  if not file: return None
41
  try: return engine.split_pdf_custom(file, range_str)
42
  except Exception as e: raise gr.Error(str(e))
43
 
44
- def interface_protect(file, password):
45
  if not file or not password: return None
46
  try: return engine.protect_pdf(file, password)
47
  except Exception as e: raise gr.Error(str(e))
48
 
49
- def interface_pdf_to_img(file):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  if not file: return None
51
  try: return engine.pdf_to_images_zip(file)
52
  except Exception as e: raise gr.Error(str(e))
53
 
54
- def interface_img_to_pdf(files):
55
  if not files: return None
56
  try: return engine.images_to_pdf(files)
57
  except Exception as e: raise gr.Error(str(e))
58
 
59
- # --- CORRECCIÓN LÓGICA ROTACIÓN (v1.6) ---
60
- def parse_angle(angle_label):
61
- """Extrae el ángulo numérico de las nuevas etiquetas descriptivas."""
62
- if not angle_label: return 0
63
- # Ejemplo: "90° (Derecha)" -> "90"
64
- try:
65
- # Cogemos lo que está antes del símbolo °
66
- num_str = angle_label.split("°")[0]
67
- return int(num_str)
68
- except:
69
- return 0
70
 
71
- def update_rotate_preview(file, angle_label):
72
  if not file: return None
73
- angle = parse_angle(angle_label)
74
- # Llama al core con el ángulo limpio
75
- return engine.get_rotated_preview(file, angle)
76
 
77
- def interface_rotate_process(file, angle_label):
78
  if not file: return None
79
- angle = parse_angle(angle_label)
80
- if angle == 0:
81
- # Si es 0, devolvemos el archivo original (o una copia)
82
- return file.name
83
- try: return engine.rotate_pdf(file, angle)
84
- except Exception as e: raise gr.Error(str(e))
85
 
86
- # --- UI ---
 
 
 
 
 
 
 
 
 
 
 
87
  with gr.Blocks(title=config.APP_TITLE, theme=gr.themes.Soft()) as demo:
88
  gr.Markdown(f"# {config.APP_TITLE}")
89
 
90
  with gr.Tabs():
91
- # 1. UNIR (Sin cambios)
92
  with gr.TabItem("Unir PDF"):
 
93
  gr.Markdown(config.TXT_MERGE)
94
  with gr.Row():
95
  with gr.Column(scale=1):
@@ -102,8 +122,9 @@ with gr.Blocks(title=config.APP_TITLE, theme=gr.themes.Soft()) as demo:
102
  merge_files.change(fn=update_file_list, inputs=merge_files, outputs=[file_table, order_input])
103
  merge_btn.click(fn=process_merge_ordered, inputs=[merge_files, order_input], outputs=merge_out)
104
 
105
- # 2. DIVIDIR (Sin cambios)
106
  with gr.TabItem("Dividir PDF"):
 
107
  gr.Markdown(config.TXT_SPLIT)
108
  with gr.Row():
109
  with gr.Column():
@@ -121,56 +142,82 @@ with gr.Blocks(title=config.APP_TITLE, theme=gr.themes.Soft()) as demo:
121
  prev_btn.click(fn=update_preview_split, inputs=[split_file, range_in, page_count], outputs=preview_gal)
122
  split_btn.click(fn=process_split_range, inputs=[split_file, range_in], outputs=split_out)
123
 
124
- # 3. ROTAR (Corregido)
125
  with gr.TabItem("Rotar PDF"):
126
- gr.Markdown("Rota el documento permanentemente. Usa '0°' para ver el original.")
 
127
  with gr.Row():
128
  with gr.Column():
129
- rot_file = gr.File(label="PDF a rotar", file_types=[".pdf"])
130
-
131
- # NUEVO: Etiquetas claras y opción 0 por defecto
132
- angle_sel = gr.Radio(
133
- choices=["0° (Original)", "90° (Derecha)", "180° (Invertir)", "270° (Izquierda)"],
134
- label="Rotación (Sentido horario)",
135
- value="0° (Original)"
136
- )
137
-
138
  rot_btn = gr.Button("Aplicar Rotación", variant="primary")
139
  with gr.Column():
140
  rot_preview = gr.Image(label="Vista Previa", height=400, type="filepath")
141
  rot_out = gr.File(label="PDF Descargable")
142
-
143
- # Eventos
144
  rot_file.change(fn=update_rotate_preview, inputs=[rot_file, angle_sel], outputs=rot_preview)
145
  angle_sel.change(fn=update_rotate_preview, inputs=[rot_file, angle_sel], outputs=rot_preview)
146
  rot_btn.click(fn=interface_rotate_process, inputs=[rot_file, angle_sel], outputs=rot_out)
147
 
148
- # 4. PROTEGER (Sin cambios)
149
- with gr.TabItem("Proteger"):
 
150
  with gr.Row():
 
151
  with gr.Column():
152
- prot_file = gr.File(label="PDF", file_types=[".pdf"])
153
- pass_in = gr.Textbox(label="Contraseña", type="password")
154
- prot_btn = gr.Button("Encriptar", variant="primary")
 
 
 
 
155
  with gr.Column():
156
- prot_out = gr.File(label="Protegido")
157
- prot_btn.click(fn=interface_protect, inputs=[prot_file, pass_in], outputs=prot_out)
158
-
159
- # 5. IMAGENES (Sin cambios)
160
- with gr.TabItem("Imágenes y PDF"):
161
- # (Mantener código anterior)
162
- gr.Markdown("Conversión entre formatos.")
 
 
 
 
 
163
  with gr.Row():
164
  with gr.Column():
165
- p2i_file = gr.File(label="PDF", file_types=[".pdf"])
166
- p2i_btn = gr.Button("Convertir a ZIP", variant="primary")
167
- p2i_out = gr.File(label="Descargar", file_types=[".zip"])
 
 
168
  with gr.Column():
169
- i2p_files = gr.File(label="Imágenes", file_count="multiple", file_types=["image"])
170
- i2p_btn = gr.Button("Crear PDF", variant="primary")
171
- i2p_out = gr.File(label="PDF Resultante")
172
- p2i_btn.click(fn=interface_pdf_to_img, inputs=p2i_file, outputs=p2i_out)
173
- i2p_btn.click(fn=interface_img_to_pdf, inputs=i2p_files, outputs=i2p_out)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
 
175
  if __name__ == "__main__":
176
  demo.launch()
 
1
+ # Versión 1.7: Suite Completa con PDF a Word y Metadatos
2
  import gradio as gr
3
  import pandas as pd
4
  import config
 
6
 
7
  engine = PDFEngine()
8
 
9
+ # --- FUNCIONES AUXILIARES (Existentes - Mantener todas) ---
10
+ def update_file_list(files): # ... (Igual v1.6)
 
11
  if not files: return pd.DataFrame(), ""
12
  data = [[i, f.split("/")[-1]] for i, f in enumerate(files)]
13
  default_order = ",".join([str(i) for i in range(len(files))])
14
  return pd.DataFrame(data, columns=["ID", "Archivo"]), default_order
15
 
16
+ def process_merge_ordered(files, order_str): # ... (Igual v1.6)
17
  if not files: return None
18
  try:
19
  indices = [int(x.strip()) for x in order_str.split(",") if x.strip().isdigit()]
20
  return engine.merge_pdfs(files, indices)
21
  except Exception as e: raise gr.Error(str(e))
22
 
23
+ def load_pdf_info(file): # ... (Igual v1.6)
24
  if not file: return None, 0, gr.update(visible=False)
25
  info = engine.get_pdf_info(file)
26
  return f"Cargado: {info['name']} ({info['pages']} págs).", info['pages'], gr.update(visible=True)
27
 
28
+ def update_preview_split(file, range_str, total_pages): # ... (Igual v1.6)
29
  if not file or not range_str: return None
30
  key_pages = engine.get_preview_indices_from_string(range_str, total_pages)
31
  if not key_pages: return None
 
35
  if path: gallery.append((path, f"Pág {p}"))
36
  return gallery
37
 
38
+ def process_split_range(file, range_str): # ... (Igual v1.6)
39
  if not file: return None
40
  try: return engine.split_pdf_custom(file, range_str)
41
  except Exception as e: raise gr.Error(str(e))
42
 
43
+ def interface_protect(file, password): # ... (Igual v1.6)
44
  if not file or not password: return None
45
  try: return engine.protect_pdf(file, password)
46
  except Exception as e: raise gr.Error(str(e))
47
 
48
+ def interface_rotate_process(file, angle_label): # ... (Igual v1.6)
49
+ if not file: return None
50
+ # Parseo manual rápido para no depender de func externa si no se copió
51
+ angle = 0
52
+ if "90" in angle_label: angle = 90
53
+ elif "180" in angle_label: angle = 180
54
+ elif "270" in angle_label: angle = 270
55
+
56
+ if angle == 0: return file.name
57
+ try: return engine.rotate_pdf(file, angle)
58
+ except Exception as e: raise gr.Error(str(e))
59
+
60
+ def update_rotate_preview(file, angle_label): # ... (Igual v1.6)
61
+ if not file: return None
62
+ angle = 0
63
+ if "90" in angle_label: angle = 90
64
+ elif "180" in angle_label: angle = 180
65
+ elif "270" in angle_label: angle = 270
66
+ return engine.get_rotated_preview(file, angle)
67
+
68
+ def interface_pdf_to_img(file): # ... (Igual v1.6)
69
  if not file: return None
70
  try: return engine.pdf_to_images_zip(file)
71
  except Exception as e: raise gr.Error(str(e))
72
 
73
+ def interface_img_to_pdf(files): # ... (Igual v1.6)
74
  if not files: return None
75
  try: return engine.images_to_pdf(files)
76
  except Exception as e: raise gr.Error(str(e))
77
 
78
+ # --- NUEVAS FUNCIONES DE INTERFAZ (v1.7) ---
79
+ def interface_pdf_to_word(file):
80
+ if not file: return None
81
+ try: return engine.pdf_to_word(file)
82
+ except Exception as e: raise gr.Error(f"Error: {e}")
 
 
 
 
 
 
83
 
84
+ def interface_extract_text(file):
85
  if not file: return None
86
+ try: return engine.extract_text(file)
87
+ except Exception as e: raise gr.Error(f"Error: {e}")
 
88
 
89
+ def interface_metadata(file, title, author, subject):
90
  if not file: return None
91
+ try: return engine.update_metadata(file, title, author, subject)
92
+ except Exception as e: raise gr.Error(f"Error: {e}")
 
 
 
 
93
 
94
+ def load_meta_info(file):
95
+ """Carga los metadatos actuales al subir el archivo."""
96
+ if not file: return "", "", ""
97
+ try:
98
+ reader = engine.get_pdf_info(file) # Reusamos este método o leemos directo
99
+ # Nota: get_pdf_info en v1.7 devuelve 'title'
100
+ # Para author y subject tendríamos que leerlo de nuevo o ampliar get_pdf_info.
101
+ # Por simplicidad, dejamos campos vacíos para rellenar.
102
+ return "", "", ""
103
+ except: return "", "", ""
104
+
105
+ # --- UI PRINCIPAL ---
106
  with gr.Blocks(title=config.APP_TITLE, theme=gr.themes.Soft()) as demo:
107
  gr.Markdown(f"# {config.APP_TITLE}")
108
 
109
  with gr.Tabs():
110
+ # 1. UNIR
111
  with gr.TabItem("Unir PDF"):
112
+ # (Igual v1.6)
113
  gr.Markdown(config.TXT_MERGE)
114
  with gr.Row():
115
  with gr.Column(scale=1):
 
122
  merge_files.change(fn=update_file_list, inputs=merge_files, outputs=[file_table, order_input])
123
  merge_btn.click(fn=process_merge_ordered, inputs=[merge_files, order_input], outputs=merge_out)
124
 
125
+ # 2. DIVIDIR
126
  with gr.TabItem("Dividir PDF"):
127
+ # (Igual v1.6)
128
  gr.Markdown(config.TXT_SPLIT)
129
  with gr.Row():
130
  with gr.Column():
 
142
  prev_btn.click(fn=update_preview_split, inputs=[split_file, range_in, page_count], outputs=preview_gal)
143
  split_btn.click(fn=process_split_range, inputs=[split_file, range_in], outputs=split_out)
144
 
145
+ # 3. ROTAR
146
  with gr.TabItem("Rotar PDF"):
147
+ # (Igual v1.6)
148
+ gr.Markdown("Rota el documento.")
149
  with gr.Row():
150
  with gr.Column():
151
+ rot_file = gr.File(label="PDF", file_types=[".pdf"])
152
+ angle_sel = gr.Radio(["0° (Original)", "90° (Derecha)", "180° (Invertir)", "270° (Izquierda)"], label="Rotación", value="0° (Original)")
 
 
 
 
 
 
 
153
  rot_btn = gr.Button("Aplicar Rotación", variant="primary")
154
  with gr.Column():
155
  rot_preview = gr.Image(label="Vista Previa", height=400, type="filepath")
156
  rot_out = gr.File(label="PDF Descargable")
 
 
157
  rot_file.change(fn=update_rotate_preview, inputs=[rot_file, angle_sel], outputs=rot_preview)
158
  angle_sel.change(fn=update_rotate_preview, inputs=[rot_file, angle_sel], outputs=rot_preview)
159
  rot_btn.click(fn=interface_rotate_process, inputs=[rot_file, angle_sel], outputs=rot_out)
160
 
161
+ # 4. HERRAMIENTAS DE TEXTO Y WORD (NUEVO)
162
+ with gr.TabItem("Word y Texto"):
163
+ gr.Markdown("Conversiones de formato y extracción de contenido.")
164
  with gr.Row():
165
+ # Columna A: PDF a Word
166
  with gr.Column():
167
+ gr.Markdown("### PDF a Word (Beta)")
168
+ gr.Markdown("_Nota: Funciona mejor con texto simple. Las tablas complejas pueden variar._")
169
+ p2w_file = gr.File(label="PDF", file_types=[".pdf"])
170
+ p2w_btn = gr.Button("Convertir a Word (.docx)", variant="primary")
171
+ p2w_out = gr.File(label="Archivo Word")
172
+
173
+ # Columna B: Extraer Texto
174
  with gr.Column():
175
+ gr.Markdown("### Extraer Texto Plano")
176
+ gr.Markdown("_Obtén el contenido sin formato en un .txt_")
177
+ ext_file = gr.File(label="PDF", file_types=[".pdf"])
178
+ ext_btn = gr.Button("Extraer TXT", variant="secondary")
179
+ ext_out = gr.File(label="Archivo de Texto")
180
+
181
+ p2w_btn.click(fn=interface_pdf_to_word, inputs=p2w_file, outputs=p2w_out)
182
+ ext_btn.click(fn=interface_extract_text, inputs=ext_file, outputs=ext_out)
183
+
184
+ # 5. METADATOS (NUEVO)
185
+ with gr.TabItem("Editar Metadatos"):
186
+ gr.Markdown("Modifica la información interna del documento.")
187
  with gr.Row():
188
  with gr.Column():
189
+ meta_file = gr.File(label="PDF", file_types=[".pdf"])
190
+ meta_title = gr.Textbox(label="Título", placeholder="Nuevo título del documento")
191
+ meta_author = gr.Textbox(label="Autor", placeholder="Nombre del autor")
192
+ meta_subject = gr.Textbox(label="Asunto", placeholder="Descripción breve")
193
+ meta_btn = gr.Button("Guardar cambios", variant="primary")
194
  with gr.Column():
195
+ meta_out = gr.File(label="PDF con nuevos metadatos")
196
+
197
+ meta_btn.click(fn=interface_metadata, inputs=[meta_file, meta_title, meta_author, meta_subject], outputs=meta_out)
198
+
199
+ # 6. PROTEGER E IMAGENES (Resto de pestañas)
200
+ with gr.TabItem("Otras Herramientas"):
201
+ with gr.Accordion("Proteger con Contraseña", open=False):
202
+ with gr.Row():
203
+ prot_file = gr.File(label="PDF")
204
+ pass_in = gr.Textbox(label="Contraseña", type="password")
205
+ prot_btn = gr.Button("Encriptar")
206
+ prot_out = gr.File()
207
+ prot_btn.click(fn=interface_protect, inputs=[prot_file, pass_in], outputs=prot_out)
208
+
209
+ with gr.Accordion("Conversión Imágenes <-> PDF", open=False):
210
+ with gr.Row():
211
+ p2i_file = gr.File(label="PDF a Imágenes")
212
+ p2i_btn = gr.Button("Convertir")
213
+ p2i_out = gr.File()
214
+ p2i_btn.click(fn=interface_pdf_to_img, inputs=p2i_file, outputs=p2i_out)
215
+
216
+ with gr.Row():
217
+ i2p_files = gr.File(label="Imágenes a PDF", file_count="multiple")
218
+ i2p_btn = gr.Button("Unir")
219
+ i2p_out = gr.File()
220
+ i2p_btn.click(fn=interface_img_to_pdf, inputs=i2p_files, outputs=i2p_out)
221
 
222
  if __name__ == "__main__":
223
  demo.launch()