streamlit-design-toolkit / pages /2_Cloudinary_Crop.py
Wkatir
feat: adding converter
065cb1a
import streamlit as st
import cloudinary
import cloudinary.uploader
import cloudinary.api
import requests
import io
import zipfile
import logging
# Configuración inicial de la página y logging
st.set_page_config(
page_title="✂️ Cloudinary Smart Crop",
page_icon="✂️",
layout="wide"
)
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
def init_cloudinary():
"""
Inicializa Cloudinary usando las credenciales definidas en st.secrets.
Realiza una limpieza inicial de recursos y registra el estado de inicialización.
"""
if 'cloudinary_initialized' not in st.session_state:
try:
cloudinary.config(url=st.secrets['CLOUDINARY_URL'])
st.session_state.cloudinary_initialized = True
cleanup_cloudinary() # Limpieza inicial de recursos
logging.info("Cloudinary inicializado correctamente.")
except Exception as e:
st.error("Error: No se encontraron las credenciales de Cloudinary en secrets.toml")
st.session_state.cloudinary_initialized = False
logging.error(f"Error al inicializar Cloudinary: {e}")
def check_file_size(file, max_size_mb=10):
"""
Verifica que el tamaño del archivo no exceda el límite especificado (en MB).
"""
file.seek(0, 2) # Mover al final del archivo
file_size = file.tell() / (1024 * 1024)
file.seek(0) # Regresar al inicio
return file_size <= max_size_mb
def cleanup_cloudinary():
"""
Limpia todos los recursos almacenados en Cloudinary.
"""
if not st.session_state.get('cloudinary_initialized', False):
return
try:
result = cloudinary.api.resources()
if 'resources' in result and result['resources']:
public_ids = [resource['public_id'] for resource in result['resources']]
if public_ids:
cloudinary.api.delete_resources(public_ids)
logging.info("Recursos de Cloudinary limpiados.")
except Exception as e:
st.error(f"Error al limpiar recursos: {e}")
logging.error(f"Error en cleanup_cloudinary: {e}")
def process_image(image, width, height, gravity_option, dpr):
"""
Procesa la imagen usando Cloudinary y retorna la imagen procesada.
Reinicia el puntero del stream y utiliza el atributo 'name' para determinar
si se debe preservar la transparencia en PNG.
"""
if not st.session_state.get('cloudinary_initialized', False):
st.error("Cloudinary no está inicializado correctamente")
return None
try:
image.seek(0)
image_name = getattr(image, 'name', '')
if not check_file_size(image, 10):
st.error(f"{image_name} excede el límite de 10MB")
return None
image_content = image.read()
# Configurar la transformación para Cloudinary
transformation = {
"width": width,
"height": height,
"crop": "fill",
"gravity": gravity_option,
"quality": 100,
"dpr": dpr,
}
if image_name.lower().endswith('.png'):
transformation["flags"] = "preserve_transparency"
else:
transformation["flags"] = None
response = cloudinary.uploader.upload(
image_content,
transformation=[transformation]
)
processed_url = response.get('secure_url')
processed_image = requests.get(processed_url).content
# Limpia el recurso procesado en Cloudinary
cloudinary.api.delete_resources([response.get('public_id')])
logging.info(f"Imagen {image_name} procesada correctamente.")
return processed_image
except Exception as e:
st.error(f"Error procesando imagen: {e}")
logging.error(f"Error en process_image para {image_name}: {e}")
return None
def main():
"""
Función principal que ejecuta la aplicación Streamlit.
"""
init_cloudinary()
st.title("✂️ Cloudinary Smart Crop")
with st.expander("📌 Instrucciones de uso", expanded=True):
st.markdown("""
**Recorta y redimensiona imágenes inteligentemente con Cloudinary**
**Formatos soportados:**
✅ PNG, JPG, JPEG, WEBP
**Características principales:**
- 🔍 Detección automática de rostros (opción 'face'/'faces')
- 🖼️ Mantenimiento de transparencia en PNG
- 📐 Redimensionado preciso con diferentes modos de gravedad
- 🚀 Procesamiento por lotes y descarga en ZIP
**Pasos para usar:**
1. ⚙️ Configura dimensiones deseadas y parámetros de recorte
2. 🎯 Selecciona el tipo de gravedad y el DPR (entre 1 y 3)
3. 📤 Sube tus imágenes (máx. 10MB c/u)
4. 🚀 Procesa y descarga los resultados
""")
# Parámetros de configuración
col1, col2, col3, col4 = st.columns(4)
with col1:
width = st.number_input("Ancho (px)", value=1000, min_value=100, max_value=3000)
with col2:
height = st.number_input("Alto (px)", value=460, min_value=100, max_value=3000)
with col3:
gravity_option = st.selectbox(
"Gravedad",
["auto", "center", "face", "faces", "north", "south", "east", "west"],
help="Configura cómo se enfocará el recorte en la imagen"
)
with col4:
dpr = st.number_input("DPR", value=3, min_value=1, max_value=3, step=1)
# Carga de imágenes
uploaded_files = st.file_uploader(
"Sube tus imágenes (máx. 10MB por archivo)",
type=['png', 'jpg', 'jpeg', 'webp'],
accept_multiple_files=True
)
if uploaded_files:
st.header("Vista Previa Original")
cols = st.columns(3)
original_images = []
for idx, file in enumerate(uploaded_files):
file_bytes = file.getvalue()
original_images.append((file.name, file_bytes))
with cols[idx % 3]:
st.image(file_bytes, caption=file.name, use_column_width=True)
if st.button("✨ Procesar Imágenes"):
processed_images = []
progress_bar = st.progress(0)
total_images = len(original_images)
for idx, (name, img_bytes) in enumerate(original_images):
st.write(f"Procesando: {name}")
img_io = io.BytesIO(img_bytes)
with st.spinner(f"Procesando {name}..."):
processed = process_image(img_io, width, height, gravity_option, dpr)
if processed:
processed_images.append((name, processed))
progress_bar.progress((idx + 1) / total_images)
if processed_images:
st.header("Resultados Finales")
cols = st.columns(3)
for idx, (name, img_bytes) in enumerate(processed_images):
with cols[idx % 3]:
st.image(img_bytes, caption=name, use_column_width=True)
# Crear archivo ZIP con las imágenes procesadas
zip_buffer = io.BytesIO()
with zipfile.ZipFile(zip_buffer, 'w') as zip_file:
for name, img_bytes in processed_images:
zip_file.writestr(f"procesada_{name}", img_bytes)
st.download_button(
label="📥 Descargar Todas",
data=zip_buffer.getvalue(),
file_name="imagenes_procesadas.zip",
mime="application/zip"
)
if __name__ == "__main__":
main()