"""Emotion-tag parsing for expressive TTS. A ``[감정]`` tag must steer the pitch/speed of the text that follows without being spoken; a bracket that is NOT a known emotion word must be kept as ordinary spoken content.""" from wsai.backends.emotion import ( EMOTION_PARAMS, match_emotion, parse_segments, ) from wsai.dashboard import _speech_text BASE = 1.3 def test_emotion_tag_is_not_spoken_and_sets_delivery(): segs = parse_segments("[기쁨] 오늘 날씨 좋다", BASE) assert len(segs) == 1 assert "기쁨" not in segs[0].text # the tag word is dropped assert segs[0].text == "오늘 날씨 좋다" mult, semis = EMOTION_PARAMS["happy"] assert segs[0].speed == BASE * mult assert segs[0].pitch == semis def test_midreply_emotion_change_splits_segments(): segs = parse_segments("[속상함] 정말 힘들었겠다. [힘차게] 하지만 넌 할 수 있어!", BASE) assert len(segs) == 2 assert segs[0].text == "정말 힘들었겠다." assert segs[1].text == "하지만 넌 할 수 있어!" assert segs[0].pitch == EMOTION_PARAMS["sad"][1] assert segs[1].pitch == EMOTION_PARAMS["hopeful"][1] def test_non_emotion_bracket_is_spoken_without_brackets(): segs = parse_segments("[기쁨] 첫째는 [1번] 항목이야", BASE) assert len(segs) == 1 # "1번" is not an emotion -> read it; brackets themselves are gone. assert "1번" in segs[0].text assert "[" not in segs[0].text and "]" not in segs[0].text assert segs[0].pitch == EMOTION_PARAMS["happy"][1] def test_text_before_first_tag_is_neutral(): segs = parse_segments("잠깐만. [신남] 찾았다!", BASE) assert segs[0].text == "잠깐만." assert segs[0].speed == BASE and segs[0].pitch == 0.0 assert segs[1].pitch == EMOTION_PARAMS["excited"][1] def test_plain_text_is_one_neutral_segment(): segs = parse_segments("그냥 평범한 문장이야", BASE) assert len(segs) == 1 assert segs[0].speed == BASE and segs[0].pitch == 0.0 def test_empty_input_yields_no_segments(): assert parse_segments("", BASE) == [] assert parse_segments(" ", BASE) == [] def test_only_emotion_tags_yield_no_segments(): # A reply that is nothing but tags has nothing to say. assert parse_segments("[기쁨][신남]", BASE) == [] def test_match_emotion_is_synonym_and_space_tolerant(): assert match_emotion("속상함") == "sad" assert match_emotion(" 힘 차게 ") == "hopeful" # squeezed + trimmed assert match_emotion("행복하게") == "happy" assert match_emotion("메모") is None # not an emotion def test_persona_examples_are_all_recognised(): # Every emotion the brain persona advertises must resolve, or it would be # read aloud instead of shaping the voice. for word in ["힘차게", "궁금", "반가움", "차분하게", "웃으며", "속상함"]: assert match_emotion(word) is not None, word def test_voice_turn_keeps_leading_emotion_tag_for_tts_parser(): text = "[속상함] 정말 힘들었겠다. [힘차게] 하지만 넌 할 수 있어!" spoken = _speech_text(text) segs = parse_segments(spoken, BASE) assert spoken == text assert segs[0].text == "정말 힘들었겠다." assert segs[0].pitch == EMOTION_PARAMS["sad"][1] assert segs[1].text == "하지만 넌 할 수 있어!" assert segs[1].pitch == EMOTION_PARAMS["hopeful"][1]