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()