import os from io import BytesIO from typing import List import funciones.motion as motion import tempfile from fastapi import FastAPI, Form from fastapi import FastAPI, File, UploadFile, HTTPException, status, BackgroundTasks from fastapi.responses import StreamingResponse, FileResponse, JSONResponse import herramientas app = FastAPI() @app.get("/health", tags=["Monitoreo Server"], description="Verifica el estado de salud de la API.", summary="Health Check" ) async def health_check(): """ Este endpoint devuelve una respuesta 200 OK para indicar que la API está funcionando. """ return JSONResponse(content={"status": "ok"}, status_code=200) @app.post("/echo-image/", tags=["Monitoreo Server"], description="Test endpoint para prueba de envío de imagenes.", summary="Mirror test para envío de imagenes" ) async def echo_image(image: UploadFile = File(...)): if not image.content_type.startswith("image/"): return {"error": "El archivo no es una imagen"} contents = await image.read() return StreamingResponse(BytesIO(contents), media_type=image.content_type) @app.post("/echo-video/", tags=["Monitoreo Server"], description="Test endpoint para prueba de envío de videos. Recibe un video y lo devuelve tal cual.", summary="Mirror test para envío de videos" ) async def echo_video(video: UploadFile = File(...)): """ Recibe un archivo de video y lo devuelve sin modificaciones. Útil para probar la conectividad y el manejo de archivos de video en la API. """ # Validar que el archivo subido es un video if not video.content_type.startswith("video/"): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="El archivo no es un video. Por favor, suba un archivo de video." ) try: # Lee el contenido binario del video contents = await video.read() # Envuelve los bytes en un buffer en memoria buffer = BytesIO(contents) buffer.seek(0) # Asegúrate de que el puntero esté al inicio para la lectura # Devuelve el video utilizando StreamingResponse con el tipo MIME correcto return StreamingResponse(buffer, media_type=video.content_type) except Exception as e: # Captura cualquier error inesperado durante la lectura o la respuesta print(f"Error inesperado en /echo-video/: {e}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Ha ocurrido un error inesperado al procesar el video." ) @app.post("/motion-image/") async def motion_image(image: UploadFile = File(...), background_tasks: BackgroundTasks = BackgroundTasks()): """ Recibe una imagen, la procesa con motion(), le da movimiento y devuelve el resultado. """ if not image.content_type.startswith("image/"): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="El archivo no es una imagen. Por favor, suba una imagen." ) temp_image_path = None # Inicializamos la variable para el bloque finally output_file_generated = False # Flag para saber si monomotion generó un archivo try: # 1. Guardar la imagen subida en un archivo temporal with tempfile.NamedTemporaryFile(delete=False, suffix=f"_{image.filename}") as tmp_file: contents = await image.read() tmp_file.write(contents) temp_image_path = tmp_file.name print(f"Imagen subida guardada temporalmente en: {temp_image_path}") # 2. Ejecutar motion() # Asumimos que motion devuelve la ruta a un archivo de salida path_archivo = await motion.motion(temp_image_path) print("Salí de monomotion con resultado: ", path_archivo) output_file_generated = True # Si llegamos aquí, asumimos que se generó un archivo # 3. Determinar el tipo MIME del archivo de salida (si es diferente al de entrada) # Esto es importante si motion cambia el formato (ej. de JPG a MP4) import mimetypes mimetypes.add_type("video/mp4", ".mp4") # Asegúrate de añadir tipos si esperas video mime_type, _ = mimetypes.guess_type(path_archivo) print("Myme type es: ", mime_type) if not mime_type: print("Entró a not mime_type") mime_type = "application/octet-stream" # Tipo genérico si no se puede adivinar # 4.5 Programar la eliminación de AMBOS archivos después de que la respuesta sea enviada # El archivo de entrada se borrará sí o sí. background_tasks.add_task(herramientas.delete_file_on_complete, temp_image_path) # El archivo de salida también se borrará, asumiendo que es temporal y solo para esta respuesta. background_tasks.add_task(herramientas.delete_file_on_complete, path_archivo) # 4. Devolver el archivo procesado al cliente return FileResponse( path=path_archivo, media_type=mime_type, filename=os.path.basename(path_archivo) ) except HTTPException: # Re-lanza cualquier HTTPException ya creada (ej. el 400 inicial) raise except Exception as e: # Captura cualquier otro error inesperado durante el proceso print(f"Error inesperado en /generate-monomotion-image/: {e}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Ha ocurrido un error interno al procesar la imagen con monomotion: {e}" ) finally: # 5. Limpieza: Eliminar archivos temporales # Eliminar la imagen de entrada temporal if temp_image_path and os.path.exists(temp_image_path): try: os.remove(temp_image_path) print(f"Archivo temporal de entrada eliminado: {temp_image_path}") except Exception as cleanup_e: print(f"Error al eliminar el archivo temporal de entrada {temp_image_path}: {cleanup_e}") @app.post("/echo-random-file/") async def echo_random_file(files: List[UploadFile] = File(...)): """ Recibe múltiples archivos, selecciona uno al azar y lo devuelve. """ if not files: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="No se enviaron archivos." ) temp_file_paths = [] # Lista para guardar las rutas de los archivos temporales try: # 1. Guardar cada UploadFile en un archivo temporal en disco for uploaded_file in files: # Creamos un archivo temporal con un sufijo para mantener la extensión # delete=False para que no se elimine inmediatamente al cerrar el archivo. # Lo eliminaremos explícitamente en el bloque finally. with tempfile.NamedTemporaryFile(delete=False, suffix=f"_{uploaded_file.filename}") as tmp_file: contents = await uploaded_file.read() tmp_file.write(contents) current_temp_path = tmp_file.name temp_file_paths.append(current_temp_path) print(f"Guardado temporalmente: {current_temp_path}") # 2. Pasar la lista de rutas de archivos temporales a tu función 'cinema' # ¡Ahora 'lista_rutas_archivos' es la "lista" que tu función cinema espera! resultado_cinema = await motion.cinema(temp_file_paths) print("182.- Resultado es: ", resultado_cinema) print("Y su tipo es: ", type(resultado_cinema)) # 3. Manejar el resultado de la función 'cinema' if isinstance(resultado_cinema, str): # Si cinema devuelve una ruta a un archivo (ej. un video generado) # Determinar el tipo MIME del archivo de salida import mimetypes mime_type, _ = mimetypes.guess_type(resultado_cinema) if not mime_type: mime_type = "application/octet-stream" # Tipo genérico si no se puede adivinar # Devolver el archivo generado por cinema # Asegúrate de que este archivo también se gestione y elimine si es temporal return FileResponse( path=resultado_cinema, media_type=mime_type, filename=os.path.basename(resultado_cinema) ) elif isinstance(resultado_cinema, dict) and "error" in resultado_cinema: # Si cinema devuelve un diccionario de error raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Error en la función cinema: {resultado_cinema['error']}" ) else: # Si cinema devuelve otra cosa (ej. un JSON de éxito sin archivo) return resultado_cinema # Asume que es un diccionario JSON serializable except HTTPException: # Re-lanza cualquier HTTPException que ya se haya levantado (ej. por validación de entrada) raise except Exception as e: # Captura cualquier otro error inesperado print(f"Error inesperado en /process-files-with-cinema/: {e}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Ha ocurrido un error interno al procesar los archivos con cinema: {e}" ) finally: # 4. Limpiar los archivos temporales creados for path in temp_file_paths: if os.path.exists(path): try: #os.remove(path) print(f"Archivo temporal eliminado: {path}") except Exception as cleanup_e: print(f"Error al eliminar el archivo temporal {path}: {cleanup_e}")