Spaces:
Running
Running
import streamlit as st | |
from PIL import Image | |
import io | |
import zipfile | |
from datetime import datetime | |
from pathlib import Path | |
import logging | |
# Configuración de la página y del logging | |
st.set_page_config( | |
page_title="📁 Image Compression Tool", | |
page_icon="📁", | |
layout="wide" | |
) | |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') | |
def optimize_image(image_file): | |
""" | |
Optimiza la imagen automáticamente manteniendo la máxima calidad visual. | |
Parámetros: | |
- image_file: Archivo de imagen (stream) subido por el usuario. | |
Retorna: | |
- Tuple con los datos de la imagen optimizada (bytes) y el formato usado. | |
- Si no se logra una reducción de tamaño, retorna (None, None). | |
""" | |
try: | |
img = Image.open(image_file) | |
output_buffer = io.BytesIO() | |
# Configuración de parámetros según el formato y modo de la imagen | |
if img.format == 'PNG': | |
if img.mode in ('RGBA', 'LA'): | |
img_to_save = img | |
save_params = {'format': 'PNG', 'optimize': True} | |
else: | |
img_to_save = img.convert('RGB') | |
save_params = {'format': 'JPEG', 'quality': 85, 'optimize': True} | |
else: | |
img_to_save = img.convert('RGB') | |
save_params = {'format': 'JPEG', 'quality': 85, 'optimize': True} | |
# Guardar la imagen optimizada en un buffer | |
img_to_save.save(output_buffer, **save_params) | |
output_buffer.seek(0) | |
optimized_size = len(output_buffer.getvalue()) | |
# Se valida que la imagen optimizada sea menor que la original | |
if optimized_size >= image_file.size: | |
logging.info("No se logró reducir el tamaño de la imagen.") | |
return None, None | |
logging.info("Imagen optimizada exitosamente.") | |
return output_buffer.getvalue(), save_params['format'] | |
except Exception as e: | |
st.error(f"Error optimizando imagen: {e}") | |
logging.error(f"Error en optimize_image: {e}") | |
return None, None | |
def process_filename(original_name, optimized_format): | |
""" | |
Genera un nuevo nombre de archivo para la imagen optimizada. | |
Parámetros: | |
- original_name: Nombre original del archivo. | |
- optimized_format: Formato de la imagen optimizada. | |
Retorna: | |
- String con el nuevo nombre (por ejemplo, 'foto_optimizado.jpg'). | |
""" | |
path = Path(original_name) | |
new_suffix = f".{optimized_format.lower()}" if optimized_format else '.jpg' | |
return f"{path.stem}_optimizado{new_suffix}" | |
def main(): | |
st.title("📁 Image Compression Tool") | |
with st.expander("📌 Instrucciones de uso", expanded=True): | |
st.markdown( | |
""" | |
**Optimización automática de imágenes con máxima calidad visual** | |
**Características:** | |
- 🔍 Compresión inteligente automática | |
- 🖼️ Mantiene transparencia en PNG | |
- 📉 Reducción de tamaño garantizada | |
- 🚀 Procesamiento por lotes | |
- 📥 Descarga múltiple en ZIP | |
""" | |
) | |
uploaded_files = st.file_uploader( | |
"Sube tus imágenes (máx. 50MB por archivo)", | |
type=['png', 'jpg', 'jpeg'], | |
accept_multiple_files=True | |
) | |
if uploaded_files and st.button("🚀 Optimizar Imágenes"): | |
processed_images = [] | |
total_reduction = 0 | |
progress_bar = st.progress(0) | |
total_files = len(uploaded_files) | |
st.info("Optimizando imágenes...") | |
logging.info(f"Se subieron {total_files} archivos para optimización.") | |
for idx, file in enumerate(uploaded_files): | |
try: | |
if file.size > 50 * 1024 * 1024: | |
st.error(f"El archivo {file.name} excede 50MB") | |
logging.warning(f"El archivo {file.name} excede el tamaño máximo permitido.") | |
continue | |
original_size = file.size | |
optimized_data, format_used = optimize_image(file) | |
if optimized_data and format_used: | |
new_size = len(optimized_data) | |
reduction = original_size - new_size | |
total_reduction += reduction | |
new_name = process_filename(file.name, format_used) | |
processed_images.append((new_name, optimized_data, original_size, new_size)) | |
st.write(f"✅ {file.name} optimizado ({reduction / 1024:.1f} KB ahorrados)") | |
logging.info(f"{file.name} optimizado. Ahorro: {reduction / 1024:.1f} KB.") | |
else: | |
st.write(f"⚠️ {file.name} no se optimizó porque no se logró reducir el tamaño.") | |
logging.info(f"{file.name} no se optimizó, no hubo reducción de tamaño.") | |
progress_bar.progress((idx + 1) / total_files) | |
except Exception as e: | |
st.error(f"Error procesando {file.name}: {e}") | |
logging.error(f"Error procesando {file.name}: {e}") | |
st.success(f"¡Optimización completada! (Ahorro total: {total_reduction / 1024:.1f} KB)") | |
if processed_images: | |
st.subheader("Resultados de Optimización") | |
cols = st.columns(3) | |
with cols[0]: | |
st.metric("Archivos procesados", len(processed_images)) | |
with cols[1]: | |
st.metric("Espacio ahorrado", f"{total_reduction / 1024:.1f} KB") | |
with cols[2]: | |
avg_reduction = (total_reduction / len(processed_images)) / 1024 | |
st.metric("Reducción promedio", f"{avg_reduction:.1f} KB") | |
# Crear archivo ZIP con las imágenes optimizadas | |
zip_buffer = io.BytesIO() | |
with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file: | |
for name, data, _, _ in processed_images: | |
zip_file.writestr(name, data) | |
st.download_button( | |
label="📥 Descargar Todas las Imágenes", | |
data=zip_buffer.getvalue(), | |
file_name=f"imagenes_optimizadas_{datetime.now().strftime('%Y%m%d_%H%M')}.zip", | |
mime="application/zip" | |
) | |
if __name__ == "__main__": | |
main() | |