DaniFera commited on
Commit
8a4f34e
verified
1 Parent(s): eb87c9f

Update core.py

Browse files
Files changed (1) hide show
  1. core.py +123 -94
core.py CHANGED
@@ -1,4 +1,4 @@
1
- # Versi贸n 1.4: A帽adidas funciones de Rotaci贸n y Conversi贸n de Im谩genes
2
  import os
3
  import zipfile
4
  from pypdf import PdfWriter, PdfReader
@@ -9,7 +9,7 @@ import uuid
9
 
10
  class PDFEngine:
11
 
12
- # --- M脡TODOS EXISTENTES (Auxiliares y anteriores) ---
13
  @staticmethod
14
  def _get_output_path(filename: str) -> str:
15
  unique_name = f"{uuid.uuid4().hex[:8]}_{filename}"
@@ -19,22 +19,54 @@ class PDFEngine:
19
  try:
20
  reader = PdfReader(file_path)
21
  return {"pages": len(reader.pages), "name": os.path.basename(file_path)}
22
- except Exception:
23
- return {"pages": 0, "name": "Error"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
 
 
25
  def generate_preview(self, file_path: str, page_number: int) -> str:
26
- # (C贸digo existente v1.3)
27
  try:
28
  images = convert_from_path(file_path, first_page=page_number, last_page=page_number, size=(None, 400))
29
  if images:
30
- output_path = self._get_output_path(f"preview_pg{page_number}.jpg")
31
- images[0].save(output_path, "JPEG")
32
- return output_path
33
  return None
34
- except Exception: return None
35
 
36
  def get_preview_indices_from_string(self, range_str: str, max_pages: int) -> list:
37
- # (C贸digo existente v1.3)
38
  key_pages = []
39
  parts = range_str.split(',')
40
  for part in parts:
@@ -52,7 +84,7 @@ class PDFEngine:
52
  return sorted(list(set(key_pages)))
53
 
54
  def merge_pdfs(self, file_paths: list, order_indices: list = None) -> str:
55
- # (C贸digo existente v1.3)
56
  if not file_paths: raise ValueError("No hay archivos.")
57
  ordered = []
58
  if order_indices and len(order_indices) == len(file_paths):
@@ -65,99 +97,96 @@ class PDFEngine:
65
  merger.close()
66
  return out
67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  def split_pdf_custom(self, file_path: str, range_str: str) -> str:
69
- # (C贸digo existente v1.3 - incluyendo _parse_range_groups interno si lo ten铆as separado,
70
- # o la l贸gica inline. Por brevedad asumo la l贸gica completa aqu铆 o que mantienes el m茅todo auxiliar)
71
- # ... Aqu铆 ir铆a la l贸gica de ZIP que hicimos en v1.3 ...
72
- # RESUMEN PARA NO REPETIR TODO EL BLOQUE:
73
- # Usa la implementaci贸n de la v1.3 que devuelve un zip_path
74
- pass
75
- # *NOTA*: Al copiar, aseg煤rate de tener el m茅todo split_pdf_custom completo de la v1.3 aqu铆.
 
 
 
 
 
 
 
 
 
 
 
 
76
 
77
  def protect_pdf(self, file_path: str, password: str) -> str:
78
- # (C贸digo existente v1.3)
79
- if not file_path or not password: raise ValueError("Datos incompletos.")
80
- reader = PdfReader(file_path)
81
- writer = PdfWriter()
82
- for page in reader.pages: writer.add_page(page)
83
- writer.encrypt(password)
84
  out = self._get_output_path("protegido.pdf")
85
- with open(out, "wb") as f: writer.write(f)
86
  return out
87
 
88
- # --- NUEVAS FUNCIONALIDADES (v1.4) ---
89
-
90
  def rotate_pdf(self, file_path: str, angle: int) -> str:
91
- """Rota todas las p谩ginas del PDF (90, 180, 270 grados horario)."""
92
  if not file_path: raise ValueError("Falta archivo.")
93
-
94
- reader = PdfReader(file_path)
95
- writer = PdfWriter()
96
-
97
- for page in reader.pages:
98
- # rotate_clockwise suma el 谩ngulo actual
99
- page.rotate(angle)
100
- writer.add_page(page)
101
-
102
- out_path = self._get_output_path(f"rotado_{angle}.pdf")
103
- with open(out_path, "wb") as f:
104
- writer.write(f)
105
- return out_path
106
 
107
  def pdf_to_images_zip(self, file_path: str) -> str:
108
- """Convierte cada p谩gina a JPG y devuelve un ZIP."""
109
- if not file_path: raise ValueError("Falta archivo.")
110
-
111
- # Convertir PDF a lista de im谩genes (Pillow objects)
112
- # Usamos 200 dpi para buena calidad sin ser excesivo
113
- try:
114
- images = convert_from_path(file_path, dpi=200)
115
- except Exception as e:
116
- raise RuntimeError(f"Error convirtiendo a imagen: {e}")
117
-
118
- base_name = os.path.basename(file_path).replace(".pdf", "")
119
- img_paths = []
120
-
121
- # Guardar im谩genes temporalmente
122
- for i, img in enumerate(images):
123
- img_name = f"{base_name}_pag_{i+1}.jpg"
124
- p = self._get_output_path(img_name)
125
  img.save(p, "JPEG")
126
- img_paths.append(p)
127
-
128
- # Crear ZIP
129
- zip_path = self._get_output_path(f"{base_name}_imagenes.zip")
130
- with zipfile.ZipFile(zip_path, 'w') as zipf:
131
- for p in img_paths:
132
- zipf.write(p, arcname=os.path.basename(p))
133
-
134
- return zip_path
135
 
136
  def images_to_pdf(self, image_paths: list) -> str:
137
- """Convierte una lista de im谩genes en un solo PDF."""
138
- if not image_paths: raise ValueError("No hay im谩genes.")
139
-
140
- try:
141
- # Abrir primera imagen
142
- img_objs = []
143
- for p in image_paths:
144
- img = Image.open(p)
145
- # Convertir a RGB por si viene en RGBA/P (PNGs transparentes dan error en PDF)
146
- if img.mode != 'RGB':
147
- img = img.convert('RGB')
148
- img_objs.append(img)
149
-
150
- out_path = self._get_output_path("album_imagenes.pdf")
151
-
152
- # Guardar usando la primera imagen como base y appending el resto
153
- if img_objs:
154
- img_objs[0].save(
155
- out_path, "PDF",
156
- resolution=100.0,
157
- save_all=True,
158
- append_images=img_objs[1:]
159
- )
160
-
161
- return out_path
162
- except Exception as e:
163
- raise RuntimeError(f"Error creando PDF: {e}")
 
1
+ # Versi贸n 1.5: A帽adido soporte para previsualizaci贸n de rotaci贸n
2
  import os
3
  import zipfile
4
  from pypdf import PdfWriter, PdfReader
 
9
 
10
  class PDFEngine:
11
 
12
+ # --- M脡TODOS EXISTENTES (Auxiliares) ---
13
  @staticmethod
14
  def _get_output_path(filename: str) -> str:
15
  unique_name = f"{uuid.uuid4().hex[:8]}_{filename}"
 
19
  try:
20
  reader = PdfReader(file_path)
21
  return {"pages": len(reader.pages), "name": os.path.basename(file_path)}
22
+ except Exception: return {"pages": 0, "name": "Error"}
23
+
24
+ # --- NUEVO M脡TODO PARA PREVIEW DE ROTACI脫N (v1.5) ---
25
+ def get_rotated_preview(self, file_path: str, angle: int) -> str:
26
+ """
27
+ Genera una vista previa de la primera p谩gina rotada.
28
+ Angle: 0, 90, 180, 270 (Sentido horario)
29
+ """
30
+ if not file_path: return None
31
+
32
+ try:
33
+ # 1. Extraemos SOLO la p谩gina 1
34
+ # Usamos size fijo para que sea r谩pido
35
+ images = convert_from_path(file_path, first_page=1, last_page=1, size=(None, 500))
36
+
37
+ if not images: return None
38
+
39
+ img = images[0]
40
+
41
+ # 2. Rotamos la imagen
42
+ # Pillow rota 'Counter Clockwise', as铆 que usamos negativo para simular 'Clockwise'
43
+ # expand=True ajusta el tama帽o del lienzo si la imagen pasa de vertical a horizontal
44
+ if angle != 0:
45
+ img = img.rotate(-angle, expand=True)
46
+
47
+ # 3. Guardamos
48
+ output_path = self._get_output_path(f"preview_rot_{angle}.jpg")
49
+ img.save(output_path, "JPEG")
50
+ return output_path
51
+
52
+ except Exception as e:
53
+ print(f"Error en preview rotaci贸n: {e}")
54
+ return None
55
 
56
+ # --- M脡TODOS DE L脫GICA DE NEGOCIO (Ya existentes, resumidos) ---
57
  def generate_preview(self, file_path: str, page_number: int) -> str:
58
+ # (C贸digo igual a v1.4)
59
  try:
60
  images = convert_from_path(file_path, first_page=page_number, last_page=page_number, size=(None, 400))
61
  if images:
62
+ out = self._get_output_path(f"preview_pg{page_number}.jpg")
63
+ images[0].save(out, "JPEG")
64
+ return out
65
  return None
66
+ except: return None
67
 
68
  def get_preview_indices_from_string(self, range_str: str, max_pages: int) -> list:
69
+ # (C贸digo igual a v1.4 - Copiar implementaci贸n completa previa)
70
  key_pages = []
71
  parts = range_str.split(',')
72
  for part in parts:
 
84
  return sorted(list(set(key_pages)))
85
 
86
  def merge_pdfs(self, file_paths: list, order_indices: list = None) -> str:
87
+ # (C贸digo igual a v1.4)
88
  if not file_paths: raise ValueError("No hay archivos.")
89
  ordered = []
90
  if order_indices and len(order_indices) == len(file_paths):
 
97
  merger.close()
98
  return out
99
 
100
+ def _parse_range_groups(self, range_str: str, max_pages: int) -> list:
101
+ # (Necesario para split_pdf_custom - Copiar de v1.3)
102
+ groups = []
103
+ parts = range_str.split(',')
104
+ for part in parts:
105
+ part = part.strip()
106
+ if not part: continue
107
+ current_group = []
108
+ if '-' in part:
109
+ try:
110
+ start, end = map(int, part.split('-'))
111
+ start = max(1, start)
112
+ end = min(max_pages, end)
113
+ if start <= end: current_group = list(range(start - 1, end))
114
+ except ValueError: continue
115
+ else:
116
+ try:
117
+ p = int(part)
118
+ if 1 <= p <= max_pages: current_group = [p - 1]
119
+ except ValueError: continue
120
+ if current_group: groups.append({"label": part, "indices": current_group})
121
+ return groups
122
+
123
  def split_pdf_custom(self, file_path: str, range_str: str) -> str:
124
+ # (C贸digo v1.3/v1.4 con ZIP)
125
+ if not file_path: raise ValueError("Falta archivo.")
126
+ reader = PdfReader(file_path)
127
+ total = len(reader.pages)
128
+ groups = self._parse_range_groups(range_str, total)
129
+ if not groups: raise ValueError("Rango inv谩lido.")
130
+ generated = []
131
+ base = os.path.basename(file_path).replace(".pdf", "")
132
+ for g in groups:
133
+ w = PdfWriter()
134
+ for i in g["indices"]: w.add_page(reader.pages[i])
135
+ safe = g["label"].replace(" ", "")
136
+ p = self._get_output_path(f"{base}_part_{safe}.pdf")
137
+ with open(p, "wb") as f: w.write(f)
138
+ generated.append(p)
139
+ zip_p = self._get_output_path(f"{base}_split.zip")
140
+ with zipfile.ZipFile(zip_p, 'w') as z:
141
+ for f in generated: z.write(f, arcname=os.path.basename(f))
142
+ return zip_p
143
 
144
  def protect_pdf(self, file_path: str, password: str) -> str:
145
+ # (C贸digo igual a v1.4)
146
+ if not file_path or not password: raise ValueError("Falta datos.")
147
+ r = PdfReader(file_path)
148
+ w = PdfWriter()
149
+ for p in r.pages: w.add_page(p)
150
+ w.encrypt(password)
151
  out = self._get_output_path("protegido.pdf")
152
+ with open(out, "wb") as f: w.write(f)
153
  return out
154
 
 
 
155
  def rotate_pdf(self, file_path: str, angle: int) -> str:
156
+ # (C贸digo igual a v1.4)
157
  if not file_path: raise ValueError("Falta archivo.")
158
+ r = PdfReader(file_path)
159
+ w = PdfWriter()
160
+ for p in r.pages:
161
+ p.rotate(angle)
162
+ w.add_page(p)
163
+ out = self._get_output_path(f"rotado_{angle}.pdf")
164
+ with open(out, "wb") as f: w.write(f)
165
+ return out
 
 
 
 
 
166
 
167
  def pdf_to_images_zip(self, file_path: str) -> str:
168
+ # (C贸digo igual a v1.4)
169
+ if not file_path: raise ValueError("Falta archivo")
170
+ imgs = convert_from_path(file_path, dpi=150) # Bajo DPI un poco para velocidad
171
+ base = os.path.basename(file_path).replace(".pdf", "")
172
+ paths = []
173
+ for i, img in enumerate(imgs):
174
+ p = self._get_output_path(f"{base}_{i+1}.jpg")
 
 
 
 
 
 
 
 
 
 
175
  img.save(p, "JPEG")
176
+ paths.append(p)
177
+ zp = self._get_output_path(f"{base}_imgs.zip")
178
+ with zipfile.ZipFile(zp, 'w') as z:
179
+ for p in paths: z.write(p, arcname=os.path.basename(p))
180
+ return zp
 
 
 
 
181
 
182
  def images_to_pdf(self, image_paths: list) -> str:
183
+ # (C贸digo igual a v1.4)
184
+ if not image_paths: raise ValueError("No imgs")
185
+ objs = []
186
+ for p in image_paths:
187
+ img = Image.open(p)
188
+ if img.mode != 'RGB': img = img.convert('RGB')
189
+ objs.append(img)
190
+ out = self._get_output_path("album.pdf")
191
+ if objs: objs[0].save(out, "PDF", resolution=100.0, save_all=True, append_images=objs[1:])
192
+ return out