Download scripts/test_english_e2e.py from BBBAKERY/ustwo-api: direct link, hf CLI and curl.
- Browser
- Download file 7.75 kB
-
https://huggingface.co/spaces/BBBAKERY/ustwo-api/resolve/main/scripts/test_english_e2e.py
- Command line
-
hf download hf://spaces/BBBAKERY/ustwo-api/scripts/test_english_e2e.py
-
curl -L -o test_english_e2e.py https://huggingface.co/spaces/BBBAKERY/ustwo-api/resolve/main/scripts/test_english_e2e.py
7.75 kB
| #!/usr/bin/env python3 | |
| """์์ด E2E ํ์ดํ๋ผ์ธ ํ ์คํธ. | |
| RAVDESS WAV + ์๋ ์์ฑ ํ ์คํธ๋ก Stage2 ์ ์ฒด ํ์ดํ๋ผ์ธ์ ํ ์คํธํ๋ค. | |
| ๊ฐ์ ๋ณ 2๊ฐ์ฉ 14๊ฐ ์ธ๊ทธ๋จผํธ๋ฅผ ํต๊ณผ์์ผ audio + text + fusion ๋์์ ๊ฒ์ฆ. | |
| Usage: | |
| python scripts/test_english_e2e.py | |
| """ | |
| from __future__ import annotations | |
| import csv | |
| import json | |
| import logging | |
| import sys | |
| from pathlib import Path | |
| PROJECT_ROOT = Path(__file__).parent.parent | |
| sys.path.insert(0, str(PROJECT_ROOT)) | |
| logging.basicConfig( | |
| level=logging.INFO, | |
| format="%(asctime)s - %(levelname)s - %(message)s", | |
| ) | |
| logger = logging.getLogger("test_english_e2e") | |
| MANIFEST_PATH = PROJECT_ROOT / "data" / "ravdess" / "manifest.csv" | |
| # ๊ฐ์ ๋ณ ๋งค์นญ ํ ์คํธ (RAVDESS๋ ํ ์คํธ ์์ผ๋ฏ๋ก ์๋ ์์ฑ) | |
| EMOTION_TEXTS = { | |
| "neutral": [ | |
| "The meeting is scheduled for three o'clock.", | |
| "I'll pick up the groceries on the way home.", | |
| ], | |
| "joy": [ | |
| "I'm so happy to hear from you!", | |
| "That's wonderful news, I'm thrilled!", | |
| ], | |
| "sadness": [ | |
| "I miss you so much, it hurts.", | |
| "I feel really down today, nothing is going right.", | |
| ], | |
| "anger": [ | |
| "I can't believe you did that, I'm so angry!", | |
| "This is completely unacceptable, stop it now!", | |
| ], | |
| "surprise": [ | |
| "Oh my god, I can't believe it!", | |
| "What?! I never expected that!", | |
| ], | |
| "fear": [ | |
| "I'm scared, something doesn't feel right.", | |
| "Please help, I'm terrified right now.", | |
| ], | |
| "disgust": [ | |
| "That's absolutely disgusting, I feel sick.", | |
| "This is revolting, I can't stand it.", | |
| ], | |
| } | |
| def pick_samples(manifest_path: Path) -> list[dict]: | |
| """๊ฐ์ ๋ณ 2๊ฐ์ฉ WAV ์ ํ.""" | |
| by_emotion: dict[str, list[dict]] = {} | |
| with open(manifest_path) as f: | |
| for row in csv.DictReader(f): | |
| emotion = row["emotion"] | |
| if emotion not in by_emotion: | |
| by_emotion[emotion] = [] | |
| if len(by_emotion[emotion]) < 2: | |
| by_emotion[emotion].append(row) | |
| samples = [] | |
| for emotion in EMOTION_TEXTS: | |
| if emotion in by_emotion: | |
| samples.extend(by_emotion[emotion]) | |
| return samples | |
| def run_e2e(): | |
| """E2E ํ ์คํธ ์คํ.""" | |
| from src.common.schemas import ( | |
| Models, | |
| ProcessingInfo, | |
| Segment, | |
| Stage1Output, | |
| ) | |
| from src.stage2.process import process | |
| if not MANIFEST_PATH.exists(): | |
| logger.error("manifest.csv๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค. ๋จผ์ prepare_ravdess.py๋ฅผ ์คํํ์ธ์.") | |
| return False | |
| samples = pick_samples(MANIFEST_PATH) | |
| logger.info(f"์ ํ๋ ํ ์คํธ ์ํ: {len(samples)}๊ฐ") | |
| # Stage1Output ์์ฑ | |
| segments = [] | |
| ground_truths = [] | |
| for i, sample in enumerate(samples): | |
| emotion = sample["emotion"] | |
| texts = EMOTION_TEXTS[emotion] | |
| text = texts[i % len(texts)] | |
| segments.append(Segment( | |
| segment_id=i, | |
| speaker_id=f"speaker_{int(sample['actor_id']) % 2}", | |
| start=float(i * 3.0), | |
| end=float(i * 3.0 + 3.0), | |
| text=text, | |
| language="en", | |
| audio_path=sample["clean_path"], | |
| confidence=0.95, | |
| )) | |
| ground_truths.append(emotion) | |
| stage1_output = Stage1Output( | |
| call_id="ravdess_e2e_test", | |
| duration=float(len(segments) * 3.0), | |
| speakers=["speaker_0", "speaker_1"], | |
| audio_path="data/ravdess/test_call.wav", | |
| segments=segments, | |
| processing_info=ProcessingInfo( | |
| processing_time_sec=1.0, | |
| models=Models( | |
| diarization="pyannote/speaker-diarization-3.1", | |
| asr="large-v3-turbo", | |
| language_id="whisper", | |
| alignment="whisperx", | |
| ), | |
| device="cpu", | |
| ), | |
| ) | |
| # Stage2 config (์์ด ์ ์ฉ) | |
| config = { | |
| "audio_emotion": { | |
| "model": "iic/emotion2vec_plus_base", | |
| }, | |
| "text_emotion": { | |
| "korean_model": "searle-j/kote_for_easygoing_people", | |
| "english_model": "j-hartmann/emotion-english-distilroberta-base", | |
| }, | |
| "fusion": { | |
| "audio_weight": 0.6, | |
| "text_weight": 0.4, | |
| }, | |
| "output_path": "data/e2e_test_output.json", | |
| } | |
| logger.info("Stage 2 process() ์คํ ์ค...") | |
| output = process(stage1_output, config=config) | |
| # ๊ฒ์ฆ | |
| logger.info("\n" + "=" * 70) | |
| logger.info("E2E ํ ์คํธ ๊ฒฐ๊ณผ") | |
| logger.info("=" * 70) | |
| errors = [] | |
| # 1. EmotionResult ๊ฐ์ ํ์ธ | |
| if len(output.emotions) != len(segments): | |
| errors.append(f"EmotionResult ๊ฐ์ ๋ถ์ผ์น: {len(output.emotions)} != {len(segments)}") | |
| else: | |
| logger.info(f"[PASS] EmotionResult ๊ฐ์: {len(output.emotions)}") | |
| # 2. SpeakerSummary ํ์ธ | |
| if len(output.speaker_summaries) > 0: | |
| logger.info(f"[PASS] SpeakerSummary ์์ฑ: {len(output.speaker_summaries)}๋ช ") | |
| else: | |
| errors.append("SpeakerSummary๊ฐ ๋น์ด์์") | |
| # 3. ๊ฐ EmotionResult ์ ํจ์ฑ ํ์ธ | |
| valid_emotions = {"neutral", "joy", "sadness", "anger", "surprise", "fear", "disgust"} | |
| for em in output.emotions: | |
| if em.fused_emotion not in valid_emotions: | |
| errors.append(f"์ ํจํ์ง ์์ fused_emotion: {em.fused_emotion}") | |
| if not (0.0 <= em.fused_confidence <= 1.0): | |
| errors.append(f"fused_confidence ๋ฒ์ ์ด๊ณผ: {em.fused_confidence}") | |
| if not any("์ ํจํ์ง ์์" in e for e in errors): | |
| logger.info("[PASS] ๋ชจ๋ EmotionResult ์ ํจ") | |
| # 4. JSON ์ง๋ ฌํ ํ ์คํธ | |
| try: | |
| json_str = output.model_dump_json(indent=2) | |
| roundtrip = json.loads(json_str) | |
| logger.info(f"[PASS] JSON ์ง๋ ฌํ/์ญ์ง๋ ฌํ ์ฑ๊ณต ({len(json_str)} bytes)") | |
| except Exception as e: | |
| errors.append(f"JSON ์ง๋ ฌํ ์คํจ: {e}") | |
| # 5. ๊ฒฐ๊ณผ ํ ์ด๋ธ | |
| logger.info(f"\n{'Seg':>3s} | {'Ground Truth':>12s} | {'Audio':>10s} | {'Text':>10s} | {'Fused':>10s} | {'Conf':>5s}") | |
| logger.info("-" * 65) | |
| audio_correct = 0 | |
| text_correct = 0 | |
| fused_correct = 0 | |
| for i, (em, gt) in enumerate(zip(output.emotions, ground_truths)): | |
| a_match = "o" if em.audio_emotion == gt else "x" | |
| t_match = "o" if em.text_emotion == gt else "x" | |
| f_match = "o" if em.fused_emotion == gt else "x" | |
| if em.audio_emotion == gt: | |
| audio_correct += 1 | |
| if em.text_emotion == gt: | |
| text_correct += 1 | |
| if em.fused_emotion == gt: | |
| fused_correct += 1 | |
| logger.info( | |
| f"{i:3d} | {gt:>12s} | {em.audio_emotion:>8s} {a_match} | " | |
| f"{em.text_emotion:>8s} {t_match} | {em.fused_emotion:>8s} {f_match} | " | |
| f"{em.fused_confidence:.3f}" | |
| ) | |
| n = len(ground_truths) | |
| logger.info(f"\nAccuracy: audio={audio_correct}/{n} text={text_correct}/{n} fused={fused_correct}/{n}") | |
| # SpeakerSummary ์ถ๋ ฅ | |
| logger.info("\nSpeaker Summaries:") | |
| for spk, summary in output.speaker_summaries.items(): | |
| logger.info(f" {spk}: dominant={summary.dominant_emotion}, " | |
| f"conf={summary.avg_confidence:.3f}, " | |
| f"dist={summary.emotion_distribution}") | |
| if errors: | |
| logger.error(f"\nFAILED โ {len(errors)} errors:") | |
| for e in errors: | |
| logger.error(f" - {e}") | |
| return False | |
| logger.info(f"\n{'='*70}") | |
| logger.info("E2E ํ ์คํธ PASS โ ์์ด ํ์ดํ๋ผ์ธ ์ ์ ๋์") | |
| logger.info(f"{'='*70}") | |
| return True | |
| if __name__ == "__main__": | |
| success = run_e2e() | |
| sys.exit(0 if success else 1) | |