File size: 6,334 Bytes
cfbce04
 
 
 
 
 
065cb1a
 
 
 
 
 
 
 
 
cfbce04
 
 
065cb1a
 
 
 
 
 
 
 
 
 
cfbce04
 
 
 
065cb1a
cfbce04
 
 
065cb1a
cfbce04
 
065cb1a
cfbce04
 
065cb1a
cfbce04
065cb1a
cfbce04
 
 
 
065cb1a
cfbce04
065cb1a
cfbce04
 
065cb1a
cfbce04
 
 
065cb1a
 
cfbce04
 
 
dded47d
065cb1a
 
 
 
 
 
 
 
 
 
cfbce04
dded47d
cfbce04
 
 
 
 
 
 
065cb1a
 
 
 
 
 
 
 
 
 
 
 
cfbce04
 
 
 
 
 
 
 
 
 
 
 
 
dded47d
065cb1a
cfbce04
dded47d
 
 
065cb1a
 
dded47d
cfbce04
dded47d
 
cfbce04
dded47d
 
 
 
cfbce04
dded47d
 
cfbce04
dded47d
065cb1a
dded47d
 
065cb1a
cfbce04
dded47d
 
 
065cb1a
 
dded47d
 
cfbce04
 
 
 
 
 
 
 
 
 
 
 
065cb1a
cfbce04
 
 
 
 
 
 
 
 
dded47d
cfbce04
 
 
 
dded47d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
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()