Music_Sonar_X / app.py
Manuel Zafra
Update app.py
cbc2e8f verified
raw
history blame
26 kB
from smolagents import CodeAgent, HfApiModel, tool
import datetime
import requests
import pytz
import yaml
import os
import pickle
import time
import gradio as gr
from tools.final_answer import FinalAnswerTool
# Herramienta para obtener la hora actual en una zona horaria
@tool
def get_current_time_in_timezone(timezone: str) -> str:
"""A tool that fetches the current local time in a specified timezone.
Args:
timezone: A string representing a valid timezone (e.g., 'America/New_York').
"""
try:
# Create timezone object
tz = pytz.timezone(timezone)
# Get current time in that timezone
local_time = datetime.datetime.now(tz).strftime("%Y-%m-%d %H:%M:%S")
return f"The current local time in {timezone} is: {local_time}"
except Exception as e:
return f"Error fetching time for timezone '{timezone}': {str(e)}"
# Herramienta para reconocer canciones usando AudD
@tool
def recognize_song(audio_path: str) -> dict:
"""Reconoce una canción a partir de un archivo de audio
Args:
audio_path: ruta al archivo de audio a reconocer
"""
AUDD_API_TOKEN = os.getenv("AUDD_API_TOKEN")
if not os.path.exists(audio_path):
return {"error": "El archivo de audio no existe"}
try:
with open(audio_path, 'rb') as file:
data = {
'api_token': AUDD_API_TOKEN,
'return': 'spotify,apple_music'
}
files = {
'file': file
}
response = requests.post('https://api.audd.io/', data=data, files=files)
if response.status_code != 200:
return {"error": f"Error en la API: {response.status_code}"}
result = response.json()
if result['status'] == 'error':
return {"error": result['error']['error_message']}
if not result.get('result'):
return {"error": "No se pudo reconocer la canción"}
song_info = result['result']
return {
"🎶 Canción": song_info.get('title', 'Desconocido'),
"🎤 Artista": song_info.get('artist', 'Desconocido'),
"📀 Álbum": song_info.get('album', 'Desconocido'),
"🎧 Spotify": song_info.get('spotify', {}).get('external_urls', {}).get('spotify', 'No disponible'),
"🍏 Apple Music": song_info.get('apple_music', {}).get('url', 'No disponible')
}
except Exception as e:
return {"error": f"Error al procesar el audio: {str(e)}"}
# Herramienta para iniciar sesión en Soundeo
@tool
def @tool
def login_soundeo() -> str:
"""Inicia sesión en Soundeo usando las credenciales almacenadas en secrets"""
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# Obtener credenciales de los secrets
username = os.getenv("SOUNDEO_USERNAME")
password = os.getenv("SOUNDEO_PASSWORD")
if not username or not password:
return "❌ No se encontraron las credenciales en los secrets"
# Configurar opciones de Chrome
chrome_options = Options()
chrome_options.add_argument("--headless")
chrome_options.add_argument("--no-sandbox")
chrome_options.add_argument("--disable-dev-shm-usage")
chrome_options.add_argument("--disable-gpu")
try:
# Inicializar el driver con WebDriverManager
service = Service(ChromeDriverManager().install())
driver = webdriver.Chrome(service=service, options=chrome_options)
# Resto del código igual...
# Navegar a Soundeo
driver.get("https://soundeo.com/login")
time.sleep(3) # Esperar a que cargue la página
# Capturar screenshot para debug
driver.save_screenshot("pre_login.png")
# Completar formulario de login
username_field = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.ID, "username"))
)
username_field.send_keys(username)
password_field = driver.find_element(By.ID, "password")
password_field.send_keys(password)
# Hacer clic en el botón de login
login_button = driver.find_element(By.XPATH, "//button[@type='submit']")
login_button.click()
# Esperar a que se complete el login
time.sleep(5)
# Capturar screenshot para verificar
driver.save_screenshot("post_login.png")
# Guardar cookies para uso futuro
pickle.dump(driver.get_cookies(), open("soundeo_cookies.pkl", "wb"))
# Verificar si el login fue exitoso
if "dashboard" in driver.current_url or "account" in driver.current_url:
result = "✅ Login exitoso en Soundeo"
else:
result = "❌ Error al iniciar sesión. Verifica tus credenciales."
driver.quit()
return result
except Exception as e:
return f"❌ Error con Selenium: {str(e)}"
# Herramienta para buscar y añadir una canción a la lista de descarga
@tool
def add_song_to_download_list(song_title: str, artist: str) -> str:
"""Busca una canción en Soundeo y la añade a la lista de descarga
Args:
song_title: título de la canción
artist: nombre del artista
"""
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# Obtener credenciales de los secrets
username = os.getenv("SOUNDEO_USERNAME")
password = os.getenv("SOUNDEO_PASSWORD")
if not username or not password:
return "❌ No se encontraron las credenciales en los secrets"
# Configurar opciones de Chrome
chrome_options = Options()
chrome_options.add_argument("--headless")
chrome_options.add_argument("--no-sandbox")
chrome_options.add_argument("--disable-dev-shm-usage")
chrome_options.add_argument("--disable-gpu")
try:
# Inicializar el driver con WebDriverManager
service = Service(ChromeDriverManager().install())
driver = webdriver.Chrome(service=service, options=chrome_options)
# Verificar si hay sesión activa
cookies_file = "soundeo_cookies.pkl"
if not os.path.exists(cookies_file):
driver.quit()
return "❌ No hay sesión activa. Primero debes iniciar sesión en Soundeo."
# Cargar cookies para mantener la sesión
driver.get("https://soundeo.com")
cookies = pickle.load(open(cookies_file, "rb"))
for cookie in cookies:
try:
driver.add_cookie(cookie)
except Exception:
# Ignorar cookies problemáticas
pass
# Refrescar para aplicar cookies
driver.refresh()
time.sleep(2)
# Verificar si la sesión sigue activa después de cargar cookies
if "login" in driver.current_url:
driver.save_screenshot("session_expired.png")
driver.quit()
return "❌ La sesión ha expirado. Por favor, inicia sesión nuevamente."
# Construir una consulta de búsqueda más precisa
# Usar comillas para búsqueda exacta de título y artista juntos
search_query = f'"{song_title}" "{artist}"'
search_query = search_query.replace(" ", "+")
# Navegar a la búsqueda con la consulta
search_url = f"https://soundeo.com/search?q={search_query}"
driver.get(search_url)
time.sleep(5) # Esperar a que carguen los resultados
# Capturar screenshot de resultados para debugging
driver.save_screenshot("search_results.png")
# Buscar si hay resultados
try:
# Verificar primero si hay mensaje de "no se encontraron resultados"
no_results = driver.find_elements(By.XPATH, "//div[contains(text(), 'No results found')]")
if no_results:
# Intentar con una búsqueda menos restrictiva
simple_query = f"{song_title} {artist}".replace(" ", "+")
driver.get(f"https://soundeo.com/search?q={simple_query}")
time.sleep(5)
driver.save_screenshot("broader_search_results.png")
# Verificar nuevamente si hay resultados
no_results = driver.find_elements(By.XPATH, "//div[contains(text(), 'No results found')]")
if no_results:
driver.quit()
return f"❌ No se encontraron resultados para '{song_title}' por '{artist}'"
# Encontrar todos los resultados para elegir la mejor coincidencia
track_items = WebDriverWait(driver, 10).until(
EC.presence_of_all_elements_located((By.CSS_SELECTOR, ".track-item"))
)
if not track_items:
driver.quit()
return f"❌ No se encontraron resultados para '{song_title}' por '{artist}'"
# Buscar la mejor coincidencia entre los resultados
best_match = None
exact_match = False
for item in track_items[:5]: # Limitar a los primeros 5 resultados
try:
title_elem = item.find_element(By.CSS_SELECTOR, ".track-title")
artist_elem = item.find_element(By.CSS_SELECTOR, ".track-artist")
item_title = title_elem.text.lower()
item_artist = artist_elem.text.lower()
# Verificar si es una coincidencia exacta
if song_title.lower() in item_title and artist.lower() in item_artist:
best_match = item
exact_match = True
break
# Si no hay coincidencia exacta, tomar el primer resultado
if best_match is None:
best_match = item
except Exception:
continue
if best_match is None:
driver.quit()
return f"❌ No se pudieron procesar los resultados para '{song_title}' por '{artist}'"
# Hacer clic en "Add to Download List"
try:
download_button = best_match.find_element(By.CSS_SELECTOR, ".download-button")
download_button.click()
time.sleep(3)
driver.save_screenshot("added_to_list.png")
# Obtener información del track añadido
track_info = best_match.find_element(By.CSS_SELECTOR, ".track-title").text
artist_info = best_match.find_element(By.CSS_SELECTOR, ".track-artist").text
match_type = "exacta" if exact_match else "aproximada"
driver.quit()
return f"✅ Añadida a la lista de descarga (coincidencia {match_type}): {track_info} - {artist_info}"
except Exception as e:
driver.save_screenshot("download_button_error.png")
driver.quit()
return f"❌ Error al añadir a la lista de descarga: {str(e)}"
except Exception as e:
driver.save_screenshot("search_error.png")
driver.quit()
return f"❌ Error al buscar resultados: {str(e)}"
except Exception as e:
return f"❌ Error con Selenium: {str(e)}"
@tool
def view_download_list() -> str:
"""Muestra la lista actual de canciones en la lista de descargas de Soundeo"""
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# Obtener credenciales de los secrets
username = os.getenv("SOUNDEO_USERNAME")
password = os.getenv("SOUNDEO_PASSWORD")
if not username or not password:
return "❌ No se encontraron las credenciales en los secrets"
# Configurar opciones de Chrome
chrome_options = Options()
chrome_options.add_argument("--headless")
chrome_options.add_argument("--no-sandbox")
chrome_options.add_argument("--disable-dev-shm-usage")
chrome_options.add_argument("--disable-gpu")
try:
# Inicializar el driver con WebDriverManager
service = Service(ChromeDriverManager().install())
driver = webdriver.Chrome(service=service, options=chrome_options)
# Verificar si hay sesión activa
cookies_file = "soundeo_cookies.pkl"
if not os.path.exists(cookies_file):
driver.quit()
return "❌ No hay sesión activa. Primero debes iniciar sesión en Soundeo."
# Cargar cookies para mantener la sesión
driver.get("https://soundeo.com")
cookies = pickle.load(open(cookies_file, "rb"))
for cookie in cookies:
try:
driver.add_cookie(cookie)
except Exception:
# Ignorar cookies problemáticas
pass
# Refrescar para aplicar cookies
driver.refresh()
time.sleep(2)
# Verificar si la sesión sigue activa después de cargar cookies
if "login" in driver.current_url:
driver.save_screenshot("session_expired.png")
driver.quit()
return "❌ La sesión ha expirado. Por favor, inicia sesión nuevamente."
# Navegar a la página de la lista de descargas
driver.get("https://soundeo.com/download-list")
time.sleep(5)
# Capturar screenshot para debugging
driver.save_screenshot("download_list.png")
# Buscar las canciones en la lista
try:
track_items = driver.find_elements(By.CSS_SELECTOR, ".track-item")
if not track_items:
driver.quit()
return "📋 Tu lista de descargas está vacía."
# Crear una lista de las canciones
download_list = []
for i, item in enumerate(track_items, 1):
try:
title = item.find_element(By.CSS_SELECTOR, ".track-title").text
artist = item.find_element(By.CSS_SELECTOR, ".track-artist").text
download_list.append(f"{i}. **{title}** - *{artist}*")
except Exception:
download_list.append(f"{i}. *[Error al obtener detalles]*")
driver.quit()
# Formatear la salida
result = "📋 **Lista de descargas actual:**\n\n"
result += "\n".join(download_list)
result += f"\n\n**Total: {len(download_list)} canciones**"
return result
except Exception as e:
driver.save_screenshot("list_error.png")
driver.quit()
return f"❌ Error al obtener la lista de descargas: {str(e)}"
except Exception as e:
return f"❌ Error con Selenium: {str(e)}"
@tool
def recognize_and_download(audio_path: str) -> str:
"""Reconoce una canción desde un archivo de audio y la añade a la lista de descargas
Args:
audio_path: ruta al archivo de audio a reconocer
"""
# Primero reconocer la canción
recognition_result = recognize_song(audio_path)
if "error" in recognition_result:
return f"❌ Error en el reconocimiento: {recognition_result['error']}"
# Obtener título y artista
song_title = recognition_result["🎶 Canción"]
artist = recognition_result["🎤 Artista"]
# Añadir a la lista de descargas
download_result = add_song_to_download_list(song_title, artist)
# Formatear la respuesta
response = f"🎵 **Canción reconocida:** {song_title}\n"
response += f"🎤 **Artista:** {artist}\n"
response += f"📀 **Álbum:** {recognition_result['📀 Álbum']}\n\n"
if "✅" in download_result:
response += f"✅ **{download_result}**\n\n"
else:
response += f"⚠️ **{download_result}**\n\n"
if recognition_result['🎧 Spotify'] != "No disponible":
response += f"🎧 [Escuchar en Spotify]({recognition_result['🎧 Spotify']})\n"
if recognition_result['🍏 Apple Music'] != "No disponible":
response += f"🍏 [Escuchar en Apple Music]({recognition_result['🍏 Apple Music']})\n"
return response
@tool
def process_audio_query(query: str) -> str:
"""Procesa una consulta relacionada con reconocimiento de audio y busca el archivo más reciente
Args:
query: la consulta del usuario sobre reconocimiento de audio
"""
# Buscar el archivo de audio más reciente en la carpeta de uploads
import os
import glob
# Normalmente Gradio guarda los archivos en una carpeta como esta
audio_files = glob.glob('/tmp/gradio/*')
if not audio_files:
return "No se encontraron archivos de audio recientes."
# Ordenar por fecha de modificación (el más reciente primero)
latest_audio = max(audio_files, key=os.path.getmtime)
# Usar la herramienta de reconocimiento con el archivo encontrado
result = recognize_song(latest_audio)
if "error" in result:
return f"Error al reconocer la canción: {result['error']}"
# Formatear la respuesta
response = f"🎵 **Canción reconocida:** {result['🎶 Canción']}\n"
response += f"🎤 **Artista:** {result['🎤 Artista']}\n"
response += f"📀 **Álbum:** {result['📀 Álbum']}\n"
if result['🎧 Spotify'] != "No disponible":
response += f"🎧 [Escuchar en Spotify]({result['🎧 Spotify']})\n"
if result['🍏 Apple Music'] != "No disponible":
response += f"🍏 [Escuchar en Apple Music]({result['🍏 Apple Music']})\n"
return response
@tool
def test_recognition_with_samples() -> str:
"""Genera un archivo de audio de prueba y lo reconoce"""
import numpy as np
import tempfile
import os
from scipy.io import wavfile
try:
# Generar un tono simple (440 Hz, nota La)
sample_rate = 44100 # 44.1 kHz
duration = 5 # 5 segundos
t = np.linspace(0, duration, int(sample_rate * duration), False)
# Crear una melodía simple con algunas frecuencias
note_a = np.sin(2 * np.pi * 440 * t) # La (A4)
note_c = np.sin(2 * np.pi * 523.25 * t) # Do (C5)
note_e = np.sin(2 * np.pi * 659.25 * t) # Mi (E5)
# Combinar en una melodía simple
melody = np.concatenate([
note_a[:sample_rate],
note_c[:sample_rate],
note_e[:sample_rate],
note_c[:sample_rate],
note_a[:sample_rate]
])
# Normalizar para evitar clipping
melody = melody * 0.3
# Guardar como archivo WAV
fd, temp_path = tempfile.mkstemp(suffix=".wav")
os.close(fd)
wavfile.write(temp_path, sample_rate, melody.astype(np.float32))
# Verificar que el archivo existe y tiene contenido
file_size = os.path.getsize(temp_path)
# Informar el resultado (en un caso real intentaríamos reconocer,
# pero como es un audio generado, probablemente no lo reconocería)
return f"✅ Archivo de audio de prueba generado correctamente, tamaño: {file_size} bytes.\n\nNota: Este es un audio sintético generado para pruebas, no una canción real."
except Exception as e:
return f"❌ Error: {str(e)}"
# Configuración del agente
final_answer = FinalAnswerTool()
model = HfApiModel(
max_tokens=2096,
temperature=0.5,
model_id='Qwen/Qwen2.5-Coder-32B-Instruct',
custom_role_conversions=None,
)
with open("prompts.yaml", 'r') as stream:
prompt_templates = yaml.safe_load(stream)
# REEMPLAZA ESTE BLOQUE COMPLETO
agent = CodeAgent(
model=model,
tools=[
final_answer,
recognize_song,
login_soundeo,
add_song_to_download_list,
get_current_time_in_timezone,
test_recognition_with_samples,
view_download_list,
recognize_and_download
],
max_steps=8,
verbosity_level=1,
grammar=None,
planning_interval=None,
name=None,
description=None,
prompt_templates=prompt_templates
)
# Después de este bloque suele venir la configuración de la interfaz Gradio
# Interfaz de usuario con Gradio
with gr.Blocks() as demo:
gr.Markdown("# 🎵 Asistente Musical - Reconocimiento y Descarga")
with gr.Tab("Reconocimiento de Música"):
with gr.Row():
audio_input = gr.Audio(type="filepath", label="Sube o graba un fragmento de audio")
with gr.Row():
recognize_btn = gr.Button("🔍 Reconocer Canción")
recognize_download_btn = gr.Button("🔍+📥 Reconocer y Añadir a Descargas")
output = gr.Markdown()
# Función para procesar el reconocimiento
def process_audio(audio_path):
if not audio_path:
return "❌ Por favor sube o graba un fragmento de audio."
try:
result = recognize_song(audio_path)
if "error" in result:
return f"Error al reconocer la canción: {result['error']}"
# Formatear la respuesta
response = f"🎵 **Canción reconocida:** {result['🎶 Canción']}\n"
response += f"🎤 **Artista:** {result['🎤 Artista']}\n"
response += f"📀 **Álbum:** {result['📀 Álbum']}\n"
if result['🎧 Spotify'] != "No disponible":
response += f"🎧 [Escuchar en Spotify]({result['🎧 Spotify']})\n"
if result['🍏 Apple Music'] != "No disponible":
response += f"🍏 [Escuchar en Apple Music]({result['🍏 Apple Music']})\n"
return response
except Exception as e:
return f"❌ Error al procesar el audio: {str(e)}"
# Función para reconocer y añadir a descargas
def process_audio_and_download(audio_path):
if not audio_path:
return "❌ Por favor sube o graba un fragmento de audio."
try:
return recognize_and_download(audio_path)
except Exception as e:
return f"❌ Error al procesar el audio: {str(e)}"
recognize_btn.click(
fn=process_audio,
inputs=[audio_input],
outputs=output
)
recognize_download_btn.click(
fn=process_audio_and_download,
inputs=[audio_input],
outputs=output
)
with gr.Tab("Gestión de Soundeo"):
gr.Markdown("### Inicia sesión y gestiona tu lista de descargas")
with gr.Row():
login_btn = gr.Button("🔑 Iniciar Sesión")
view_list_btn = gr.Button("📋 Ver Lista de Descargas")
with gr.Row():
song_title = gr.Textbox(label="Título de la canción")
artist = gr.Textbox(label="Artista")
add_btn = gr.Button("➕ Añadir a Descargas")
soundeo_output = gr.Markdown()
def login_process():
return login_soundeo()
def view_list_process():
return view_download_list()
def add_to_list_process(title, artist):
if not title or not artist:
return "❌ Por favor ingresa tanto el título como el artista."
return add_song_to_download_list(title, artist)
login_btn.click(
fn=login_process,
inputs=[],
outputs=soundeo_output
)
view_list_btn.click(
fn=view_list_process,
inputs=[],
outputs=soundeo_output
)
add_btn.click(
fn=add_to_list_process,
inputs=[song_title, artist],
outputs=soundeo_output
)
with gr.Tab("Pruebas con Muestras"):
gr.Markdown("### Probar con muestras de música")
test_btn = gr.Button("Descargar Muestra de Prueba")
test_results = gr.Markdown()
def direct_test():
# Ejecuta la función directamente en lugar de usar el agente
return test_recognition_with_samples()
test_btn.click(fn=direct_test, inputs=[], outputs=test_results)
demo.launch()