VoiceSementle / test_analyze_voice.py
Sungjoon Lee
[DOCS] λ¬Έμ„œ μˆ˜μ • 및 디버그 μ½”λ“œ 제거
48b92eb
"""
Gradio Test Page for analyze_voice_logic Function
Tests the core voice analysis logic independently
"""
import gradio as gr
import asyncio
import uuid
from datetime import datetime, timedelta
from pathlib import Path
import sys
import logging
from contextlib import AsyncExitStack
# Import from backend
from backend import analyze_voice_logic, voicekit_session, session_stack
import backend
# Setup logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# MCP initialization flag
mcp_initialized = False
async def initialize_mcp():
"""Initialize VoiceKit MCP connection if not already initialized"""
global mcp_initialized
if mcp_initialized and backend.voicekit_session is not None:
logger.info("MCP already initialized")
return
logger.info("Initializing VoiceKit MCP connection...")
backend.session_stack = AsyncExitStack()
try:
from mcp.client.sse import sse_client
from mcp.client.session import ClientSession
voicekit_url = "https://mcp-1st-birthday-voicekit.hf.space/gradio_api/mcp/sse"
read, write = await backend.session_stack.enter_async_context(sse_client(voicekit_url))
backend.voicekit_session = await backend.session_stack.enter_async_context(
ClientSession(read, write)
)
await backend.voicekit_session.initialize()
# List available tools
tools_result = await backend.voicekit_session.list_tools()
logger.info(f"βœ“ VoiceKit MCP connected. Tools: {[t.name for t in tools_result.tools]}")
mcp_initialized = True
except Exception as e:
logger.error(f"Failed to initialize VoiceKit MCP: {e}")
backend.voicekit_session = None
raise
def format_result_display(result: dict) -> str:
"""Format the analysis result for display"""
if result.get("status") == "error":
return f"❌ **μ—λŸ¬ λ°œμƒ**\n\n{result.get('message', 'Unknown error')}"
# Success case
output = f"""
# βœ… 뢄석 μ™„λ£Œ!
## πŸ“Š 점수 κ²°κ³Ό
- **전체 점수**: {result.get('overall', 0)}/100
- **μŒλ†’μ΄ (Pitch)**: {result.get('pitch', 0)}/100
- **리듬 (Rhythm)**: {result.get('rhythm', 0)}/100
- **μ—λ„ˆμ§€ (Energy)**: {result.get('energy', 0)}/100
- **발음 (Pronunciation)**: {result.get('pronunciation', 0)}/100
- **λŒ€μ‚¬ 정확도 (Transcript)**: {result.get('transcript', 0)}/100
## 🎯 결과
- **μΉ΄ν…Œκ³ λ¦¬**: {result.get('category', 'N/A')}
- **μ •λ‹΅ μ—¬λΆ€**: {'βœ… μ •λ‹΅!' if result.get('is_correct') else '❌ μ˜€λ‹΅'}
## πŸ’‘ μ‘°μ–Έ
{result.get('advice', '쑰언이 μ—†μŠ΅λ‹ˆλ‹€.')}
## 🎨 힌트
{result.get('hints', 'νžŒνŠΈκ°€ μ—†μŠ΅λ‹ˆλ‹€.')}
"""
return output
def get_available_dates():
"""Get list of available puzzle dates"""
# Return last 30 days as options
dates = []
today = datetime.now()
for i in range(30):
date = today - timedelta(days=i)
dates.append(date.strftime("%Y-%m-%d"))
return dates
async def test_analyze_voice(audio_file, date: str, session_id: str, generate_new_session: bool):
"""
Test the analyze_voice_logic function
Args:
audio_file: Uploaded audio file (gr.Audio component returns file path)
date: Date string in YYYY-MM-DD format
session_id: Session ID (UUID)
generate_new_session: If True, generate a new session ID
"""
try:
# Initialize MCP if not already done
await initialize_mcp()
# Generate new session ID if requested
if generate_new_session or not session_id:
session_id = str(uuid.uuid4())
# Read audio file
if audio_file is None:
return "❌ μ˜€λ””μ˜€ νŒŒμΌμ„ μ—…λ‘œλ“œν•΄μ£Όμ„Έμš”.", session_id
# Read audio bytes
audio_path = Path(audio_file)
if not audio_path.exists():
return f"❌ μ˜€λ””μ˜€ νŒŒμΌμ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€: {audio_file}", session_id
with open(audio_path, "rb") as f:
audio_bytes = f.read()
# print(f"πŸ“ Audio file size: {len(audio_bytes)} bytes")
# print(f"πŸ“… Date: {date}")
# print(f"πŸ†” Session ID: {session_id}")
# Call the function
result = await analyze_voice_logic(audio_bytes, date, session_id)
# Format and return result
formatted_result = format_result_display(result)
return formatted_result, session_id
except Exception as e:
import traceback
error_msg = f"❌ **μ˜ˆμ™Έ λ°œμƒ**\n\n```\n{str(e)}\n\n{traceback.format_exc()}\n```"
return error_msg, session_id
def sync_test_analyze_voice(audio_file, date: str, session_id: str, generate_new_session: bool):
"""Synchronous wrapper for async function"""
return asyncio.run(test_analyze_voice(audio_file, date, session_id, generate_new_session))
# Gradio Interface
with gr.Blocks(title="Analyze Voice Logic Test") as demo:
gr.Markdown("# 🎀 Analyze Voice Logic Test")
gr.Markdown("Test the `analyze_voice_logic` function from `backend.py`")
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("## πŸ“₯ μž…λ ₯")
# Audio input
audio_input = gr.Audio(
label="μ˜€λ””μ˜€ 파일",
type="filepath",
sources=["upload", "microphone"]
)
# Date selection
date_input = gr.Dropdown(
choices=get_available_dates(),
label="λ‚ μ§œ 선택",
value=datetime.now().strftime("%Y-%m-%d"),
allow_custom_value=True
)
# Session ID
with gr.Row():
session_id_input = gr.Textbox(
label="μ„Έμ…˜ ID (UUID)",
value=str(uuid.uuid4()),
interactive=True
)
generate_session_btn = gr.Checkbox(
label="μƒˆ μ„Έμ…˜ ID 생성",
value=False
)
# Submit button
submit_btn = gr.Button("πŸš€ 뢄석 μ‹œμž‘", variant="primary", size="lg")
with gr.Column(scale=2):
gr.Markdown("## πŸ“Š κ²°κ³Ό")
result_output = gr.Markdown(
"κ²°κ³Όκ°€ 여기에 ν‘œμ‹œλ©λ‹ˆλ‹€.",
label="뢄석 κ²°κ³Ό"
)
# Info section
with gr.Accordion("ℹ️ μ‚¬μš© 방법", open=False):
gr.Markdown("""
1. **μ˜€λ””μ˜€ 파일**: ν…ŒμŠ€νŠΈν•  μŒμ„± νŒŒμΌμ„ μ—…λ‘œλ“œν•˜κ±°λ‚˜ 마이크둜 λ…ΉμŒν•˜μ„Έμš”
2. **λ‚ μ§œ**: 퍼즐이 μžˆλŠ” λ‚ μ§œλ₯Ό μ„ νƒν•˜μ„Έμš” (DB에 ν•΄λ‹Ή λ‚ μ§œμ˜ 퍼즐이 μžˆμ–΄μ•Ό ν•©λ‹ˆλ‹€)
3. **μ„Έμ…˜ ID**: μžλ™ μƒμ„±λ˜κ±°λ‚˜ 직접 μž…λ ₯ν•  수 μžˆμŠ΅λ‹ˆλ‹€
4. **뢄석 μ‹œμž‘**: λ²„νŠΌμ„ ν΄λ¦­ν•˜μ—¬ 뢄석을 μ‹œμž‘ν•©λ‹ˆλ‹€
### μ£Όμ˜μ‚¬ν•­
- 이 νŽ˜μ΄μ§€λŠ” λ…λ¦½μ μœΌλ‘œ μ‹€ν–‰λ©λ‹ˆλ‹€ (Backend μ„œλ²„ λΆˆν•„μš”)
- MCP 연결은 μžλ™μœΌλ‘œ μ΄ˆκΈ°ν™”λ©λ‹ˆλ‹€
- λ°μ΄ν„°λ² μ΄μŠ€μ— ν•΄λ‹Ή λ‚ μ§œμ˜ 퍼즐이 μžˆμ–΄μ•Ό ν•©λ‹ˆλ‹€
- VoiceKit MCP (μ™ΈλΆ€ μ„œλ²„)와 Gemini APIκ°€ 정상 μž‘λ™ν•΄μ•Ό ν•©λ‹ˆλ‹€
""")
with gr.Accordion("πŸ” 디버그 정보", open=False):
gr.Markdown("""
### μƒνƒœ 확인
- Database: PostgreSQL μ—°κ²° 확인 (`docker ps | grep voice-komentle-db`)
- MCP: VoiceKit MCP μ„œλ²„ (μ™ΈλΆ€) - μžλ™ μ—°κ²°
- Gemini: GOOGLE_API_KEY ν™˜κ²½λ³€μˆ˜ 확인
### ν•„μˆ˜ ν™˜κ²½ λ³€μˆ˜
```bash
DATABASE_URL=postgresql://user:pass@host:port/dbname
GOOGLE_API_KEY=your_api_key_here
```
""")
# Event handler
submit_btn.click(
fn=sync_test_analyze_voice,
inputs=[audio_input, date_input, session_id_input, generate_session_btn],
outputs=[result_output, session_id_input]
)
# Auto-generate new session ID when checkbox is checked
def update_session_id(generate_new):
if generate_new:
return str(uuid.uuid4())
return gr.skip()
generate_session_btn.change(
fn=update_session_id,
inputs=[generate_session_btn],
outputs=[session_id_input]
)
if __name__ == "__main__":
# print("=" * 60)
# print("🎀 Analyze Voice Logic Test Page")
# print("=" * 60)
# print("\nβœ… 사전 μ€€λΉ„:")
# print(" 1. PostgreSQL μ‹€ν–‰: docker-compose up -d postgres")
# print(" 2. ν™˜κ²½λ³€μˆ˜ μ„€μ •: GOOGLE_API_KEY, DATABASE_URL")
# print(" 3. VoiceKit MCP μ„œλ²„ (μ™ΈλΆ€): μžλ™ μ—°κ²°")
# print("\nπŸ’‘ 이 νŽ˜μ΄μ§€λŠ” λ…λ¦½μ μœΌλ‘œ μ‹€ν–‰λ©λ‹ˆλ‹€ (Backend μ„œλ²„ λΆˆν•„μš”)")
# print("\n" + "=" * 60 + "\n")
demo.launch(
server_name="127.0.0.1",
server_port=7861, # Different port from main gradio_ui.py
share=False
)