Wkatir commited on
Commit
065cb1a
·
1 Parent(s): 59ac13d

feat: adding converter

Browse files
pages/1_Cloudinary_AI.py CHANGED
@@ -5,60 +5,85 @@ import cloudinary.api
5
  import requests
6
  import io
7
  import zipfile
 
 
 
 
 
 
 
 
 
8
 
9
 
10
  def init_cloudinary():
11
- """Inicializa Cloudinary con credenciales desde secrets"""
 
 
 
12
  if 'cloudinary_initialized' not in st.session_state:
13
  try:
14
  cloudinary.config(url=st.secrets['CLOUDINARY_URL'])
15
  st.session_state.cloudinary_initialized = True
16
- cleanup_cloudinary()
 
17
  except Exception as e:
18
  st.error("Error: No se encontraron las credenciales de Cloudinary en secrets.toml")
19
  st.session_state.cloudinary_initialized = False
 
20
 
21
 
22
  def check_file_size(file, max_size_mb=10):
23
- file.seek(0, 2)
 
 
 
24
  file_size = file.tell() / (1024 * 1024)
25
  file.seek(0)
26
  return file_size <= max_size_mb
27
 
28
 
29
  def cleanup_cloudinary():
30
- """Limpia todos los recursos de Cloudinary"""
 
 
31
  if not st.session_state.get('cloudinary_initialized', False):
32
  return
33
-
34
  try:
35
  result = cloudinary.api.resources()
36
  if 'resources' in result and result['resources']:
37
  public_ids = [resource['public_id'] for resource in result['resources']]
38
  if public_ids:
39
  cloudinary.api.delete_resources(public_ids)
 
40
  except Exception as e:
41
  st.error(f"Error al limpiar recursos: {e}")
 
42
 
43
 
44
  def process_image(image, width, height, dpr):
45
  """
46
- Procesa la imagen usando Cloudinary. Se asegura de reiniciar el puntero del archivo.
47
- Retorna una tupla (imagen_procesada_bytes, extension_del_archivo).
 
 
 
 
 
 
 
48
  """
49
  if not st.session_state.get('cloudinary_initialized', False):
50
  st.error("Cloudinary no está inicializado correctamente")
51
  return None, None
52
 
53
  try:
54
- # Reiniciar el puntero para garantizar la lectura completa
55
  image.seek(0)
56
  if not check_file_size(image, 10):
57
  st.error("La imagen excede el límite de 10MB")
58
  return None, None
59
 
60
  image_content = image.read()
61
-
62
  response = cloudinary.uploader.upload(
63
  image_content,
64
  transformation=[{
@@ -71,52 +96,57 @@ def process_image(image, width, height, dpr):
71
  "flags": "preserve_transparency"
72
  }]
73
  )
74
-
75
  processed_url = response['secure_url']
76
  processed_image = requests.get(processed_url).content
77
- file_format = response.get('format', 'jpg') # Se obtiene el formato real procesado
 
78
  return processed_image, file_format
79
  except Exception as e:
80
  st.error(f"Error procesando imagen: {e}")
 
81
  return None, None
82
 
83
 
84
  def main():
 
 
 
85
  init_cloudinary()
86
 
87
  st.title("🤖 Cloudinary AI Background Generator")
88
 
89
  with st.expander("📌 ¿Cómo usar esta herramienta?", expanded=True):
90
- st.markdown("""
91
- **Transforma tus imágenes automáticamente con IA:**
92
-
93
- Esta herramienta utiliza la IA de Cloudinary para:
94
- - 🔄 Redimensionar imágenes manteniendo la relación de aspecto
95
- - 🎨 Generar fondos coherentes con la imagen usando IA
96
- - 📥 Descargar múltiples imágenes procesadas en un ZIP
97
-
98
- **Formatos soportados:**
99
- ✅ PNG, JPG, JPEG, WEBP
100
-
101
- **Pasos para usar:**
102
- 1. ⚙️ Define las dimensiones deseadas (ancho y alto)
103
- 2. 📤 Sube tus imágenes (hasta 10MB c/u)
104
- 3. 🚀 Haz clic en "Procesar Imágenes"
105
- 4. ⏬ Descarga los resultados finales
106
-
107
- **Características clave:**
108
- - Mantiene transparencia en PNGs
109
- - Soporte para formatos modernos (WEBP)
110
- - Calidad ultra HD (DPR configurable entre 1 y 3)
111
- - Procesamiento por lotes
112
- - Fondo generado por IA se adapta al contexto
113
-
114
- **Notas importantes:**
115
- - Las imágenes subidas se borrarán automáticamente después del procesamiento
116
- - Para mejores resultados con IA, usa imágenes con sujetos bien definidos
117
- - El tiempo de procesamiento varía según el tamaño y cantidad de imágenes
118
- """)
119
 
 
120
  col1, col2, col3 = st.columns(3)
121
  with col1:
122
  width = st.number_input("Ancho (px)", value=1000, min_value=100, max_value=3000)
@@ -125,6 +155,7 @@ def main():
125
  with col3:
126
  dpr = st.number_input("DPR", value=3, min_value=1, max_value=3, step=1)
127
 
 
128
  uploaded_files = st.file_uploader(
129
  "Sube tus imágenes (máx. 10MB por archivo)",
130
  type=['png', 'jpg', 'jpeg', 'webp'],
@@ -134,13 +165,12 @@ def main():
134
  if uploaded_files:
135
  st.header("Imágenes Originales")
136
  cols = st.columns(3)
137
- # Guardar el contenido original para evitar que se consuma el stream
138
  original_images = []
139
  for idx, file in enumerate(uploaded_files):
140
  file_bytes = file.getvalue()
141
  original_images.append((file.name, file_bytes))
142
  with cols[idx % 3]:
143
- st.image(file_bytes)
144
 
145
  if st.button("Procesar Imágenes"):
146
  if not st.session_state.get('cloudinary_initialized', False):
@@ -151,7 +181,6 @@ def main():
151
  progress_bar = st.progress(0)
152
 
153
  for idx, (name, img_bytes) in enumerate(original_images):
154
- # Crear un nuevo objeto BytesIO para cada imagen
155
  img_io = io.BytesIO(img_bytes)
156
  with st.spinner(f'Procesando imagen {idx + 1}/{len(original_images)}...'):
157
  processed, file_format = process_image(img_io, width, height, dpr)
@@ -164,8 +193,9 @@ def main():
164
  cols = st.columns(3)
165
  for idx, (img_bytes, file_format) in enumerate(processed_images):
166
  with cols[idx % 3]:
167
- st.image(img_bytes)
168
 
 
169
  zip_buffer = io.BytesIO()
170
  with zipfile.ZipFile(zip_buffer, 'w') as zip_file:
171
  for idx, (img_bytes, file_format) in enumerate(processed_images):
 
5
  import requests
6
  import io
7
  import zipfile
8
+ import logging
9
+
10
+ # Configuración de la página y del logging
11
+ st.set_page_config(
12
+ page_title="Cloudinary AI Background Generator",
13
+ page_icon="🤖",
14
+ layout="wide"
15
+ )
16
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
17
 
18
 
19
  def init_cloudinary():
20
+ """
21
+ Inicializa Cloudinary utilizando las credenciales definidas en st.secrets.
22
+ Registra en session_state si se inicializó correctamente y realiza una limpieza inicial.
23
+ """
24
  if 'cloudinary_initialized' not in st.session_state:
25
  try:
26
  cloudinary.config(url=st.secrets['CLOUDINARY_URL'])
27
  st.session_state.cloudinary_initialized = True
28
+ cleanup_cloudinary() # Limpieza de recursos al iniciar
29
+ logging.info("Cloudinary inicializado correctamente.")
30
  except Exception as e:
31
  st.error("Error: No se encontraron las credenciales de Cloudinary en secrets.toml")
32
  st.session_state.cloudinary_initialized = False
33
+ logging.error(f"Error al inicializar Cloudinary: {e}")
34
 
35
 
36
  def check_file_size(file, max_size_mb=10):
37
+ """
38
+ Verifica que el tamaño del archivo no exceda el límite especificado (en MB).
39
+ """
40
+ file.seek(0, io.SEEK_END)
41
  file_size = file.tell() / (1024 * 1024)
42
  file.seek(0)
43
  return file_size <= max_size_mb
44
 
45
 
46
  def cleanup_cloudinary():
47
+ """
48
+ Limpia todos los recursos almacenados en Cloudinary.
49
+ """
50
  if not st.session_state.get('cloudinary_initialized', False):
51
  return
 
52
  try:
53
  result = cloudinary.api.resources()
54
  if 'resources' in result and result['resources']:
55
  public_ids = [resource['public_id'] for resource in result['resources']]
56
  if public_ids:
57
  cloudinary.api.delete_resources(public_ids)
58
+ logging.info("Recursos de Cloudinary limpiados correctamente.")
59
  except Exception as e:
60
  st.error(f"Error al limpiar recursos: {e}")
61
+ logging.error(f"Error al limpiar recursos: {e}")
62
 
63
 
64
  def process_image(image, width, height, dpr):
65
  """
66
+ Procesa la imagen utilizando Cloudinary aplicando una transformación definida.
67
+
68
+ Parámetros:
69
+ - image: Objeto tipo BytesIO con la imagen a procesar.
70
+ - width, height: Dimensiones deseadas.
71
+ - dpr: Escalado de resolución.
72
+
73
+ Retorna:
74
+ - Tuple con la imagen procesada en bytes y la extensión del archivo.
75
  """
76
  if not st.session_state.get('cloudinary_initialized', False):
77
  st.error("Cloudinary no está inicializado correctamente")
78
  return None, None
79
 
80
  try:
 
81
  image.seek(0)
82
  if not check_file_size(image, 10):
83
  st.error("La imagen excede el límite de 10MB")
84
  return None, None
85
 
86
  image_content = image.read()
 
87
  response = cloudinary.uploader.upload(
88
  image_content,
89
  transformation=[{
 
96
  "flags": "preserve_transparency"
97
  }]
98
  )
 
99
  processed_url = response['secure_url']
100
  processed_image = requests.get(processed_url).content
101
+ file_format = response.get('format', 'jpg')
102
+ logging.info("Imagen procesada correctamente.")
103
  return processed_image, file_format
104
  except Exception as e:
105
  st.error(f"Error procesando imagen: {e}")
106
+ logging.error(f"Error en process_image: {e}")
107
  return None, None
108
 
109
 
110
  def main():
111
+ """
112
+ Función principal que configura la interfaz de la aplicación y coordina el flujo de procesamiento.
113
+ """
114
  init_cloudinary()
115
 
116
  st.title("🤖 Cloudinary AI Background Generator")
117
 
118
  with st.expander("📌 ¿Cómo usar esta herramienta?", expanded=True):
119
+ st.markdown(
120
+ """
121
+ **Transforma tus imágenes automáticamente con IA:**
122
+ - 🔄 Redimensiona manteniendo la relación de aspecto
123
+ - 🎨 Genera fondos coherentes usando IA
124
+ - 📥 Descarga múltiples imágenes en un ZIP
125
+
126
+ **Formatos soportados:**
127
+ - PNG, JPG, JPEG, WEBP
128
+
129
+ **Pasos para usar:**
130
+ 1. Define las dimensiones deseadas (ancho y alto)
131
+ 2. Sube tus imágenes (hasta 10MB cada una)
132
+ 3. Haz clic en "Procesar Imágenes"
133
+ 4. Descarga los resultados finales
134
+
135
+ **Características clave:**
136
+ - Preserva transparencia en PNGs
137
+ - Soporte para formatos modernos (WEBP)
138
+ - Calidad ultra HD (DPR configurable entre 1 y 3)
139
+ - Procesamiento por lotes
140
+ - Fondo generado por IA adaptado al contexto
141
+
142
+ **Notas:**
143
+ - Las imágenes subidas se borran automáticamente después del procesamiento
144
+ - Para mejores resultados, usa imágenes con sujetos bien definidos
145
+ - El tiempo de procesamiento varía según el tamaño y cantidad de imágenes
146
+ """
147
+ )
148
 
149
+ # Sección de parámetros de configuración
150
  col1, col2, col3 = st.columns(3)
151
  with col1:
152
  width = st.number_input("Ancho (px)", value=1000, min_value=100, max_value=3000)
 
155
  with col3:
156
  dpr = st.number_input("DPR", value=3, min_value=1, max_value=3, step=1)
157
 
158
+ # Carga de archivos de imagen
159
  uploaded_files = st.file_uploader(
160
  "Sube tus imágenes (máx. 10MB por archivo)",
161
  type=['png', 'jpg', 'jpeg', 'webp'],
 
165
  if uploaded_files:
166
  st.header("Imágenes Originales")
167
  cols = st.columns(3)
 
168
  original_images = []
169
  for idx, file in enumerate(uploaded_files):
170
  file_bytes = file.getvalue()
171
  original_images.append((file.name, file_bytes))
172
  with cols[idx % 3]:
173
+ st.image(file_bytes, caption=file.name, use_column_width=True)
174
 
175
  if st.button("Procesar Imágenes"):
176
  if not st.session_state.get('cloudinary_initialized', False):
 
181
  progress_bar = st.progress(0)
182
 
183
  for idx, (name, img_bytes) in enumerate(original_images):
 
184
  img_io = io.BytesIO(img_bytes)
185
  with st.spinner(f'Procesando imagen {idx + 1}/{len(original_images)}...'):
186
  processed, file_format = process_image(img_io, width, height, dpr)
 
193
  cols = st.columns(3)
194
  for idx, (img_bytes, file_format) in enumerate(processed_images):
195
  with cols[idx % 3]:
196
+ st.image(img_bytes, caption=f"Formato: {file_format}", use_column_width=True)
197
 
198
+ # Empaquetado en ZIP
199
  zip_buffer = io.BytesIO()
200
  with zipfile.ZipFile(zip_buffer, 'w') as zip_file:
201
  for idx, (img_bytes, file_format) in enumerate(processed_images):
pages/2_Cloudinary_Crop.py CHANGED
@@ -5,47 +5,67 @@ import cloudinary.api
5
  import requests
6
  import io
7
  import zipfile
 
 
 
 
 
 
 
 
 
8
 
9
 
10
  def init_cloudinary():
11
- """Inicializa Cloudinary con credenciales desde secrets"""
 
 
 
12
  if 'cloudinary_initialized' not in st.session_state:
13
  try:
14
  cloudinary.config(url=st.secrets['CLOUDINARY_URL'])
15
  st.session_state.cloudinary_initialized = True
16
- cleanup_cloudinary()
 
17
  except Exception as e:
18
  st.error("Error: No se encontraron las credenciales de Cloudinary en secrets.toml")
19
  st.session_state.cloudinary_initialized = False
 
20
 
21
 
22
  def check_file_size(file, max_size_mb=10):
23
- file.seek(0, 2)
 
 
 
24
  file_size = file.tell() / (1024 * 1024)
25
- file.seek(0)
26
  return file_size <= max_size_mb
27
 
28
 
29
  def cleanup_cloudinary():
30
- """Limpia todos los recursos de Cloudinary"""
 
 
31
  if not st.session_state.get('cloudinary_initialized', False):
32
  return
33
-
34
  try:
35
  result = cloudinary.api.resources()
36
  if 'resources' in result and result['resources']:
37
  public_ids = [resource['public_id'] for resource in result['resources']]
38
  if public_ids:
39
  cloudinary.api.delete_resources(public_ids)
 
40
  except Exception as e:
41
  st.error(f"Error al limpiar recursos: {e}")
 
42
 
43
 
44
  def process_image(image, width, height, gravity_option, dpr):
45
  """
46
  Procesa la imagen usando Cloudinary y retorna la imagen procesada.
47
- Se reinicia el puntero del stream y se utiliza el atributo 'name'
48
- para determinar si se debe preservar la transparencia en PNG.
49
  """
50
  if not st.session_state.get('cloudinary_initialized', False):
51
  st.error("Cloudinary no está inicializado correctamente")
@@ -60,31 +80,43 @@ def process_image(image, width, height, gravity_option, dpr):
60
 
61
  image_content = image.read()
62
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  response = cloudinary.uploader.upload(
64
  image_content,
65
- transformation=[{
66
- "width": width,
67
- "height": height,
68
- "crop": "fill",
69
- "gravity": gravity_option,
70
- "quality": 100,
71
- "dpr": dpr,
72
- "flags": "preserve_transparency" if image_name.lower().endswith('.png') else None
73
- }]
74
  )
75
 
76
- processed_url = response['secure_url']
77
  processed_image = requests.get(processed_url).content
78
 
79
  # Limpia el recurso procesado en Cloudinary
80
- cloudinary.api.delete_resources([response['public_id']])
 
81
  return processed_image
 
82
  except Exception as e:
83
  st.error(f"Error procesando imagen: {e}")
 
84
  return None
85
 
86
 
87
  def main():
 
 
 
88
  init_cloudinary()
89
 
90
  st.title("✂️ Cloudinary Smart Crop")
@@ -109,6 +141,7 @@ def main():
109
  4. 🚀 Procesa y descarga los resultados
110
  """)
111
 
 
112
  col1, col2, col3, col4 = st.columns(4)
113
  with col1:
114
  width = st.number_input("Ancho (px)", value=1000, min_value=100, max_value=3000)
@@ -123,6 +156,7 @@ def main():
123
  with col4:
124
  dpr = st.number_input("DPR", value=3, min_value=1, max_value=3, step=1)
125
 
 
126
  uploaded_files = st.file_uploader(
127
  "Sube tus imágenes (máx. 10MB por archivo)",
128
  type=['png', 'jpg', 'jpeg', 'webp'],
@@ -137,27 +171,30 @@ def main():
137
  file_bytes = file.getvalue()
138
  original_images.append((file.name, file_bytes))
139
  with cols[idx % 3]:
140
- st.image(file_bytes, caption=file.name)
141
 
142
  if st.button("✨ Procesar Imágenes"):
143
  processed_images = []
144
  progress_bar = st.progress(0)
 
145
 
146
  for idx, (name, img_bytes) in enumerate(original_images):
147
  st.write(f"Procesando: {name}")
148
  img_io = io.BytesIO(img_bytes)
149
- processed = process_image(img_io, width, height, gravity_option, dpr)
150
- if processed:
151
- processed_images.append((name, processed))
152
- progress_bar.progress((idx + 1) / len(original_images))
 
153
 
154
  if processed_images:
155
  st.header("Resultados Finales")
156
  cols = st.columns(3)
157
  for idx, (name, img_bytes) in enumerate(processed_images):
158
  with cols[idx % 3]:
159
- st.image(img_bytes, caption=name)
160
 
 
161
  zip_buffer = io.BytesIO()
162
  with zipfile.ZipFile(zip_buffer, 'w') as zip_file:
163
  for name, img_bytes in processed_images:
@@ -167,8 +204,7 @@ def main():
167
  label="📥 Descargar Todas",
168
  data=zip_buffer.getvalue(),
169
  file_name="imagenes_procesadas.zip",
170
- mime="application/zip",
171
- type="primary"
172
  )
173
 
174
 
 
5
  import requests
6
  import io
7
  import zipfile
8
+ import logging
9
+
10
+ # Configuración inicial de la página y logging
11
+ st.set_page_config(
12
+ page_title="✂️ Cloudinary Smart Crop",
13
+ page_icon="✂️",
14
+ layout="wide"
15
+ )
16
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
17
 
18
 
19
  def init_cloudinary():
20
+ """
21
+ Inicializa Cloudinary usando las credenciales definidas en st.secrets.
22
+ Realiza una limpieza inicial de recursos y registra el estado de inicialización.
23
+ """
24
  if 'cloudinary_initialized' not in st.session_state:
25
  try:
26
  cloudinary.config(url=st.secrets['CLOUDINARY_URL'])
27
  st.session_state.cloudinary_initialized = True
28
+ cleanup_cloudinary() # Limpieza inicial de recursos
29
+ logging.info("Cloudinary inicializado correctamente.")
30
  except Exception as e:
31
  st.error("Error: No se encontraron las credenciales de Cloudinary en secrets.toml")
32
  st.session_state.cloudinary_initialized = False
33
+ logging.error(f"Error al inicializar Cloudinary: {e}")
34
 
35
 
36
  def check_file_size(file, max_size_mb=10):
37
+ """
38
+ Verifica que el tamaño del archivo no exceda el límite especificado (en MB).
39
+ """
40
+ file.seek(0, 2) # Mover al final del archivo
41
  file_size = file.tell() / (1024 * 1024)
42
+ file.seek(0) # Regresar al inicio
43
  return file_size <= max_size_mb
44
 
45
 
46
  def cleanup_cloudinary():
47
+ """
48
+ Limpia todos los recursos almacenados en Cloudinary.
49
+ """
50
  if not st.session_state.get('cloudinary_initialized', False):
51
  return
 
52
  try:
53
  result = cloudinary.api.resources()
54
  if 'resources' in result and result['resources']:
55
  public_ids = [resource['public_id'] for resource in result['resources']]
56
  if public_ids:
57
  cloudinary.api.delete_resources(public_ids)
58
+ logging.info("Recursos de Cloudinary limpiados.")
59
  except Exception as e:
60
  st.error(f"Error al limpiar recursos: {e}")
61
+ logging.error(f"Error en cleanup_cloudinary: {e}")
62
 
63
 
64
  def process_image(image, width, height, gravity_option, dpr):
65
  """
66
  Procesa la imagen usando Cloudinary y retorna la imagen procesada.
67
+ Reinicia el puntero del stream y utiliza el atributo 'name' para determinar
68
+ si se debe preservar la transparencia en PNG.
69
  """
70
  if not st.session_state.get('cloudinary_initialized', False):
71
  st.error("Cloudinary no está inicializado correctamente")
 
80
 
81
  image_content = image.read()
82
 
83
+ # Configurar la transformación para Cloudinary
84
+ transformation = {
85
+ "width": width,
86
+ "height": height,
87
+ "crop": "fill",
88
+ "gravity": gravity_option,
89
+ "quality": 100,
90
+ "dpr": dpr,
91
+ }
92
+ if image_name.lower().endswith('.png'):
93
+ transformation["flags"] = "preserve_transparency"
94
+ else:
95
+ transformation["flags"] = None
96
+
97
  response = cloudinary.uploader.upload(
98
  image_content,
99
+ transformation=[transformation]
 
 
 
 
 
 
 
 
100
  )
101
 
102
+ processed_url = response.get('secure_url')
103
  processed_image = requests.get(processed_url).content
104
 
105
  # Limpia el recurso procesado en Cloudinary
106
+ cloudinary.api.delete_resources([response.get('public_id')])
107
+ logging.info(f"Imagen {image_name} procesada correctamente.")
108
  return processed_image
109
+
110
  except Exception as e:
111
  st.error(f"Error procesando imagen: {e}")
112
+ logging.error(f"Error en process_image para {image_name}: {e}")
113
  return None
114
 
115
 
116
  def main():
117
+ """
118
+ Función principal que ejecuta la aplicación Streamlit.
119
+ """
120
  init_cloudinary()
121
 
122
  st.title("✂️ Cloudinary Smart Crop")
 
141
  4. 🚀 Procesa y descarga los resultados
142
  """)
143
 
144
+ # Parámetros de configuración
145
  col1, col2, col3, col4 = st.columns(4)
146
  with col1:
147
  width = st.number_input("Ancho (px)", value=1000, min_value=100, max_value=3000)
 
156
  with col4:
157
  dpr = st.number_input("DPR", value=3, min_value=1, max_value=3, step=1)
158
 
159
+ # Carga de imágenes
160
  uploaded_files = st.file_uploader(
161
  "Sube tus imágenes (máx. 10MB por archivo)",
162
  type=['png', 'jpg', 'jpeg', 'webp'],
 
171
  file_bytes = file.getvalue()
172
  original_images.append((file.name, file_bytes))
173
  with cols[idx % 3]:
174
+ st.image(file_bytes, caption=file.name, use_column_width=True)
175
 
176
  if st.button("✨ Procesar Imágenes"):
177
  processed_images = []
178
  progress_bar = st.progress(0)
179
+ total_images = len(original_images)
180
 
181
  for idx, (name, img_bytes) in enumerate(original_images):
182
  st.write(f"Procesando: {name}")
183
  img_io = io.BytesIO(img_bytes)
184
+ with st.spinner(f"Procesando {name}..."):
185
+ processed = process_image(img_io, width, height, gravity_option, dpr)
186
+ if processed:
187
+ processed_images.append((name, processed))
188
+ progress_bar.progress((idx + 1) / total_images)
189
 
190
  if processed_images:
191
  st.header("Resultados Finales")
192
  cols = st.columns(3)
193
  for idx, (name, img_bytes) in enumerate(processed_images):
194
  with cols[idx % 3]:
195
+ st.image(img_bytes, caption=name, use_column_width=True)
196
 
197
+ # Crear archivo ZIP con las imágenes procesadas
198
  zip_buffer = io.BytesIO()
199
  with zipfile.ZipFile(zip_buffer, 'w') as zip_file:
200
  for name, img_bytes in processed_images:
 
204
  label="📥 Descargar Todas",
205
  data=zip_buffer.getvalue(),
206
  file_name="imagenes_procesadas.zip",
207
+ mime="application/zip"
 
208
  )
209
 
210
 
pages/3_Image_Compression_Tool.py CHANGED
@@ -4,57 +4,74 @@ import io
4
  import zipfile
5
  from datetime import datetime
6
  from pathlib import Path
 
 
 
 
 
 
 
 
 
7
 
8
 
9
  def optimize_image(image_file):
10
- """Optimiza la imagen automáticamente manteniendo la máxima calidad visual"""
 
 
 
 
 
 
 
 
 
11
  try:
12
  img = Image.open(image_file)
13
  output_buffer = io.BytesIO()
14
 
15
- # Configuración simple por formato
16
  if img.format == 'PNG':
17
- # Para PNG, mantener transparencia si existe
18
  if img.mode in ('RGBA', 'LA'):
19
  img_to_save = img
20
- save_params = {
21
- 'format': 'PNG',
22
- 'optimize': True
23
- }
24
  else:
25
  img_to_save = img.convert('RGB')
26
- save_params = {
27
- 'format': 'JPEG',
28
- 'quality': 85,
29
- 'optimize': True
30
- }
31
  else:
32
- # Para otros formatos, convertir a JPEG
33
  img_to_save = img.convert('RGB')
34
- save_params = {
35
- 'format': 'JPEG',
36
- 'quality': 85,
37
- 'optimize': True
38
- }
39
 
40
- # Guardar imagen optimizada
41
  img_to_save.save(output_buffer, **save_params)
42
  output_buffer.seek(0)
43
 
44
- # Validar reducción de tamaño
45
  optimized_size = len(output_buffer.getvalue())
 
46
  if optimized_size >= image_file.size:
 
47
  return None, None
48
 
 
49
  return output_buffer.getvalue(), save_params['format']
50
 
51
  except Exception as e:
52
- st.error(f"Error optimizando imagen: {str(e)}")
 
53
  return None, None
54
 
55
 
56
  def process_filename(original_name, optimized_format):
57
- """Genera nombre de archivo optimizado usando pathlib y el formato optimizado"""
 
 
 
 
 
 
 
 
 
58
  path = Path(original_name)
59
  new_suffix = f".{optimized_format.lower()}" if optimized_format else '.jpg'
60
  return f"{path.stem}_optimizado{new_suffix}"
@@ -64,16 +81,18 @@ def main():
64
  st.title("📁 Image Compression Tool")
65
 
66
  with st.expander("📌 Instrucciones de uso", expanded=True):
67
- st.markdown("""
68
- **Optimización automática de imágenes con máxima calidad visual**
69
-
70
- **Características:**
71
- - 🔍 Compresión inteligente automática
72
- - 🖼️ Mantiene transparencia en PNG
73
- - 📉 Reducción de tamaño garantizada
74
- - 🚀 Procesamiento por lotes
75
- - 📥 Descarga múltiple en ZIP
76
- """)
 
 
77
 
78
  uploaded_files = st.file_uploader(
79
  "Sube tus imágenes (máx. 50MB por archivo)",
@@ -88,11 +107,13 @@ def main():
88
  total_files = len(uploaded_files)
89
 
90
  st.info("Optimizando imágenes...")
 
91
 
92
  for idx, file in enumerate(uploaded_files):
93
  try:
94
  if file.size > 50 * 1024 * 1024:
95
- st.error(f"Archivo {file.name} excede 50MB")
 
96
  continue
97
 
98
  original_size = file.size
@@ -107,18 +128,20 @@ def main():
107
  processed_images.append((new_name, optimized_data, original_size, new_size))
108
 
109
  st.write(f"✅ {file.name} optimizado ({reduction / 1024:.1f} KB ahorrados)")
 
110
  else:
111
  st.write(f"⚠️ {file.name} no se optimizó porque no se logró reducir el tamaño.")
 
112
 
113
  progress_bar.progress((idx + 1) / total_files)
114
 
115
  except Exception as e:
116
- st.error(f"Error procesando {file.name}: {str(e)}")
 
117
 
118
  st.success(f"¡Optimización completada! (Ahorro total: {total_reduction / 1024:.1f} KB)")
119
 
120
  if processed_images:
121
- # Mostrar resultados
122
  st.subheader("Resultados de Optimización")
123
  cols = st.columns(3)
124
  with cols[0]:
@@ -129,13 +152,12 @@ def main():
129
  avg_reduction = (total_reduction / len(processed_images)) / 1024
130
  st.metric("Reducción promedio", f"{avg_reduction:.1f} KB")
131
 
132
- # Generar ZIP
133
  zip_buffer = io.BytesIO()
134
  with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
135
  for name, data, _, _ in processed_images:
136
  zip_file.writestr(name, data)
137
 
138
- # Botón de descarga
139
  st.download_button(
140
  label="📥 Descargar Todas las Imágenes",
141
  data=zip_buffer.getvalue(),
 
4
  import zipfile
5
  from datetime import datetime
6
  from pathlib import Path
7
+ import logging
8
+
9
+ # Configuración de la página y del logging
10
+ st.set_page_config(
11
+ page_title="📁 Image Compression Tool",
12
+ page_icon="📁",
13
+ layout="wide"
14
+ )
15
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
16
 
17
 
18
  def optimize_image(image_file):
19
+ """
20
+ Optimiza la imagen automáticamente manteniendo la máxima calidad visual.
21
+
22
+ Parámetros:
23
+ - image_file: Archivo de imagen (stream) subido por el usuario.
24
+
25
+ Retorna:
26
+ - Tuple con los datos de la imagen optimizada (bytes) y el formato usado.
27
+ - Si no se logra una reducción de tamaño, retorna (None, None).
28
+ """
29
  try:
30
  img = Image.open(image_file)
31
  output_buffer = io.BytesIO()
32
 
33
+ # Configuración de parámetros según el formato y modo de la imagen
34
  if img.format == 'PNG':
 
35
  if img.mode in ('RGBA', 'LA'):
36
  img_to_save = img
37
+ save_params = {'format': 'PNG', 'optimize': True}
 
 
 
38
  else:
39
  img_to_save = img.convert('RGB')
40
+ save_params = {'format': 'JPEG', 'quality': 85, 'optimize': True}
 
 
 
 
41
  else:
 
42
  img_to_save = img.convert('RGB')
43
+ save_params = {'format': 'JPEG', 'quality': 85, 'optimize': True}
 
 
 
 
44
 
45
+ # Guardar la imagen optimizada en un buffer
46
  img_to_save.save(output_buffer, **save_params)
47
  output_buffer.seek(0)
48
 
 
49
  optimized_size = len(output_buffer.getvalue())
50
+ # Se valida que la imagen optimizada sea menor que la original
51
  if optimized_size >= image_file.size:
52
+ logging.info("No se logró reducir el tamaño de la imagen.")
53
  return None, None
54
 
55
+ logging.info("Imagen optimizada exitosamente.")
56
  return output_buffer.getvalue(), save_params['format']
57
 
58
  except Exception as e:
59
+ st.error(f"Error optimizando imagen: {e}")
60
+ logging.error(f"Error en optimize_image: {e}")
61
  return None, None
62
 
63
 
64
  def process_filename(original_name, optimized_format):
65
+ """
66
+ Genera un nuevo nombre de archivo para la imagen optimizada.
67
+
68
+ Parámetros:
69
+ - original_name: Nombre original del archivo.
70
+ - optimized_format: Formato de la imagen optimizada.
71
+
72
+ Retorna:
73
+ - String con el nuevo nombre (por ejemplo, 'foto_optimizado.jpg').
74
+ """
75
  path = Path(original_name)
76
  new_suffix = f".{optimized_format.lower()}" if optimized_format else '.jpg'
77
  return f"{path.stem}_optimizado{new_suffix}"
 
81
  st.title("📁 Image Compression Tool")
82
 
83
  with st.expander("📌 Instrucciones de uso", expanded=True):
84
+ st.markdown(
85
+ """
86
+ **Optimización automática de imágenes con máxima calidad visual**
87
+
88
+ **Características:**
89
+ - 🔍 Compresión inteligente automática
90
+ - 🖼️ Mantiene transparencia en PNG
91
+ - 📉 Reducción de tamaño garantizada
92
+ - 🚀 Procesamiento por lotes
93
+ - 📥 Descarga múltiple en ZIP
94
+ """
95
+ )
96
 
97
  uploaded_files = st.file_uploader(
98
  "Sube tus imágenes (máx. 50MB por archivo)",
 
107
  total_files = len(uploaded_files)
108
 
109
  st.info("Optimizando imágenes...")
110
+ logging.info(f"Se subieron {total_files} archivos para optimización.")
111
 
112
  for idx, file in enumerate(uploaded_files):
113
  try:
114
  if file.size > 50 * 1024 * 1024:
115
+ st.error(f"El archivo {file.name} excede 50MB")
116
+ logging.warning(f"El archivo {file.name} excede el tamaño máximo permitido.")
117
  continue
118
 
119
  original_size = file.size
 
128
  processed_images.append((new_name, optimized_data, original_size, new_size))
129
 
130
  st.write(f"✅ {file.name} optimizado ({reduction / 1024:.1f} KB ahorrados)")
131
+ logging.info(f"{file.name} optimizado. Ahorro: {reduction / 1024:.1f} KB.")
132
  else:
133
  st.write(f"⚠️ {file.name} no se optimizó porque no se logró reducir el tamaño.")
134
+ logging.info(f"{file.name} no se optimizó, no hubo reducción de tamaño.")
135
 
136
  progress_bar.progress((idx + 1) / total_files)
137
 
138
  except Exception as e:
139
+ st.error(f"Error procesando {file.name}: {e}")
140
+ logging.error(f"Error procesando {file.name}: {e}")
141
 
142
  st.success(f"¡Optimización completada! (Ahorro total: {total_reduction / 1024:.1f} KB)")
143
 
144
  if processed_images:
 
145
  st.subheader("Resultados de Optimización")
146
  cols = st.columns(3)
147
  with cols[0]:
 
152
  avg_reduction = (total_reduction / len(processed_images)) / 1024
153
  st.metric("Reducción promedio", f"{avg_reduction:.1f} KB")
154
 
155
+ # Crear archivo ZIP con las imágenes optimizadas
156
  zip_buffer = io.BytesIO()
157
  with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
158
  for name, data, _, _ in processed_images:
159
  zip_file.writestr(name, data)
160
 
 
161
  st.download_button(
162
  label="📥 Descargar Todas las Imágenes",
163
  data=zip_buffer.getvalue(),
pages/4_Image_Converter.py ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from PIL import Image, UnidentifiedImageError
3
+ import io
4
+ import zipfile
5
+ import logging
6
+
7
+ # Configuración inicial de la página y logging
8
+ st.set_page_config(
9
+ page_title="Image Converter",
10
+ page_icon="🖼️",
11
+ layout="wide"
12
+ )
13
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
14
+
15
+
16
+ def convert_image(image_file, output_format):
17
+ """
18
+ Verifica la integridad del archivo y lo convierte al formato de salida deseado.
19
+
20
+ Parámetros:
21
+ - image_file: objeto BytesIO del archivo de imagen.
22
+ - output_format: cadena con el formato de salida deseado ('png' o 'jpg').
23
+
24
+ Retorna:
25
+ - Los bytes de la imagen convertida o None si ocurre algún error.
26
+ """
27
+ image_name = getattr(image_file, 'name', 'Imagen sin nombre')
28
+ try:
29
+ # Intentamos abrir y verificar la integridad de la imagen
30
+ image_file.seek(0)
31
+ image = Image.open(image_file)
32
+ image.verify() # Esto lanza una excepción si el archivo está dañado
33
+ logging.info(f"{image_name} verificado correctamente.")
34
+
35
+ # Reiniciamos el puntero y volvemos a abrir la imagen para poder manipularla
36
+ image_file.seek(0)
37
+ image = Image.open(image_file)
38
+ except (UnidentifiedImageError, Exception) as e:
39
+ st.error(f"El archivo {image_name} está dañado o es inválido: {e}")
40
+ logging.error(f"Error al verificar {image_name}: {e}")
41
+ return None
42
+
43
+ # Para JPG es necesario que la imagen esté en modo RGB
44
+ if output_format.lower() == "jpg" and image.mode != "RGB":
45
+ image = image.convert("RGB")
46
+
47
+ output_io = io.BytesIO()
48
+ try:
49
+ image.save(output_io, format=output_format.upper())
50
+ logging.info(f"{image_name} convertido a {output_format}.")
51
+ return output_io.getvalue()
52
+ except Exception as e:
53
+ st.error(f"Error al convertir {image_name}: {e}")
54
+ logging.error(f"Error al guardar {image_name}: {e}")
55
+ return None
56
+
57
+
58
+ def main():
59
+ st.title("Conversor de Formatos de Imagen")
60
+ st.markdown("Convierte tus imágenes al formato deseado y verifica la integridad de cada archivo.")
61
+
62
+ # Selección del formato de salida
63
+ output_format = st.selectbox(
64
+ "Formato de salida",
65
+ ["png", "jpg"],
66
+ help="Selecciona el formato al que deseas convertir la imagen"
67
+ )
68
+
69
+ # Carga de imágenes
70
+ uploaded_files = st.file_uploader(
71
+ "Sube tus imágenes (Formatos soportados: PNG, JPG, JPEG, WEBP)",
72
+ type=['png', 'jpg', 'jpeg', 'webp'],
73
+ accept_multiple_files=True
74
+ )
75
+
76
+ if uploaded_files:
77
+ st.header("Vista Previa Original")
78
+ cols = st.columns(3)
79
+ original_images = []
80
+ for idx, file in enumerate(uploaded_files):
81
+ file_bytes = file.getvalue()
82
+ original_images.append((file.name, file_bytes))
83
+ with cols[idx % 3]:
84
+ st.image(file_bytes, caption=file.name, use_column_width=True)
85
+
86
+ if st.button("✨ Convertir Imágenes"):
87
+ converted_images = []
88
+ for name, file_bytes in original_images:
89
+ st.write(f"Procesando: {name}")
90
+ img_io = io.BytesIO(file_bytes)
91
+ output_bytes = convert_image(img_io, output_format)
92
+ if output_bytes:
93
+ converted_images.append((name, output_bytes))
94
+
95
+ if converted_images:
96
+ st.header("Imágenes Convertidas")
97
+ cols = st.columns(3)
98
+ for idx, (name, img_bytes) in enumerate(converted_images):
99
+ with cols[idx % 3]:
100
+ st.image(img_bytes, caption=f"{name} convertido a {output_format}", use_column_width=True)
101
+
102
+ # Empaquetar las imágenes convertidas en un ZIP
103
+ zip_buffer = io.BytesIO()
104
+ with zipfile.ZipFile(zip_buffer, 'w') as zip_file:
105
+ for name, img_bytes in converted_images:
106
+ # Se ajusta el nombre del archivo con la extensión deseada
107
+ base_name = name.rsplit('.', 1)[0]
108
+ zip_file.writestr(f"{base_name}.{output_format}", img_bytes)
109
+
110
+ st.download_button(
111
+ label="📥 Descargar todas las imágenes convertidas",
112
+ data=zip_buffer.getvalue(),
113
+ file_name="imagenes_convertidas.zip",
114
+ mime="application/zip"
115
+ )
116
+
117
+
118
+ if __name__ == "__main__":
119
+ main()