lilblueyes commited on
Commit
879a21c
·
1 Parent(s): b0558d5

Add brick tests and CLI runners

Browse files
scripts/bootstrap.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ from pathlib import Path
5
+
6
+
7
+ REPO_ROOT = Path(__file__).resolve().parents[1]
8
+ if str(REPO_ROOT) not in sys.path:
9
+ sys.path.insert(0, str(REPO_ROOT))
10
+
scripts/test_asl_brick.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import json
5
+
6
+ import bootstrap # noqa: F401
7
+
8
+ from signspeak.pipeline import run_asl_video
9
+
10
+
11
+ def main() -> None:
12
+ parser = argparse.ArgumentParser(description="Run only the ASL video brick.")
13
+ parser.add_argument("video", nargs="?", default=None, help="Video path. Creates a demo clip if omitted.")
14
+ args = parser.parse_args()
15
+
16
+ intent_json, result, summary = run_asl_video(args.video)
17
+ print(summary)
18
+ print("\nIntent JSON:")
19
+ print(intent_json)
20
+ print("\nFull ASL output:")
21
+ print(json.dumps(result, indent=2, ensure_ascii=False))
22
+
23
+
24
+ if __name__ == "__main__":
25
+ main()
scripts/test_full_pipeline.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+
5
+ import bootstrap # noqa: F401
6
+
7
+ from signspeak.llm import generate_subtitle_and_instruction
8
+ from signspeak.pipeline import run_asl_video
9
+ from signspeak.tts import generate_tts
10
+
11
+
12
+ def main() -> None:
13
+ parser = argparse.ArgumentParser(description="Run ASL -> llama.cpp -> Qwen3-TTS.")
14
+ parser.add_argument("video", nargs="?", default=None, help="Video path. Creates a demo clip if omitted.")
15
+ parser.add_argument("--language", default="English")
16
+ parser.add_argument("--speaker", default="Ryan")
17
+ args = parser.parse_args()
18
+
19
+ intent_json, _, summary = run_asl_video(args.video)
20
+ subtitle, instruction, _ = generate_subtitle_and_instruction(intent_json)
21
+ audio_path = generate_tts(subtitle, args.language, args.speaker, instruction)
22
+
23
+ print(summary)
24
+ print(f"Subtitle: {subtitle}")
25
+ print(f"Voice instruction: {instruction}")
26
+ print(f"Audio: {audio_path}")
27
+
28
+
29
+ if __name__ == "__main__":
30
+ main()
scripts/test_llm_brick.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ from pathlib import Path
5
+
6
+ import bootstrap # noqa: F401
7
+
8
+ from signspeak.llm import generate_subtitle_and_instruction
9
+ from signspeak.pipeline import DEFAULT_INTENT, json_text
10
+
11
+
12
+ def main() -> None:
13
+ parser = argparse.ArgumentParser(description="Run only the llama.cpp intent-to-text brick.")
14
+ parser.add_argument("--intent", help="Path to an intent JSON file. Uses default mock intent if omitted.")
15
+ args = parser.parse_args()
16
+
17
+ intent_json = Path(args.intent).read_text(encoding="utf-8") if args.intent else json_text(DEFAULT_INTENT)
18
+ subtitle, instruction, raw = generate_subtitle_and_instruction(intent_json)
19
+ print(f"Subtitle: {subtitle}")
20
+ print(f"Voice instruction: {instruction}")
21
+ print(f"Structured output: {raw}")
22
+
23
+
24
+ if __name__ == "__main__":
25
+ main()
scripts/test_tts_brick.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+
5
+ import bootstrap # noqa: F401
6
+
7
+ from signspeak.tts import generate_tts
8
+
9
+
10
+ def main() -> None:
11
+ parser = argparse.ArgumentParser(description="Run only the Qwen3-TTS brick.")
12
+ parser.add_argument("--text", default="I am happy to see you.")
13
+ parser.add_argument("--instruction", default="Speak warmly, joyfully, and clearly.")
14
+ parser.add_argument("--language", default="English")
15
+ parser.add_argument("--speaker", default="Ryan")
16
+ args = parser.parse_args()
17
+
18
+ output_path = generate_tts(args.text, args.language, args.speaker, args.instruction)
19
+ print(output_path)
20
+
21
+
22
+ if __name__ == "__main__":
23
+ main()
tests/test_asl_pipeline.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from signspeak.asl.pipeline import build_intent_input
2
+ from signspeak.pipeline import summarize_asl_result
3
+
4
+
5
+ def test_build_intent_input_matches_llm_schema():
6
+ asl = {
7
+ "status": "ok",
8
+ "gloss_sequence": ["I", "HAPPY", "SEE", "YOU"],
9
+ "confidence": 0.91,
10
+ "frames_used": 30,
11
+ }
12
+ emotion = {
13
+ "status": "ok",
14
+ "dominant_emotion": "happy",
15
+ "intensity": 0.82,
16
+ "emotion_scores": {"happy": 0.82, "neutral": 0.18},
17
+ }
18
+
19
+ intent = build_intent_input(asl, emotion)
20
+
21
+ assert intent["detected_glosses"] == ["I", "HAPPY", "SEE", "YOU"]
22
+ assert intent["detected_facial_expression"] == "happy"
23
+ assert intent["emotion_profile"]["confidence"] == 0.82
24
+ assert intent["sign_confidence"] == 0.91
25
+ assert intent["diagnostics"]["asl_status"] == "ok"
26
+
27
+
28
+ def test_summarize_asl_result_is_stable_for_missing_fields():
29
+ summary = summarize_asl_result(
30
+ {
31
+ "asl": {"status": "model_missing"},
32
+ "emotion": {"dominant_emotion": "unknown", "intensity": 0.0},
33
+ }
34
+ )
35
+
36
+ assert "ASL status: model_missing" in summary
37
+ assert "Emotion: unknown (0.00)" in summary
38
+
tests/test_llm_parsing.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from signspeak.llm import extract_json_object, normalize_llm_output, safe_json_loads
2
+
3
+
4
+ def test_extract_json_object_from_markdown_fence():
5
+ raw = """```json
6
+ {"subtitle": "I'm happy to see you!", "voice_instruction": "Speak warmly."}
7
+ ```"""
8
+
9
+ parsed = extract_json_object(raw)
10
+
11
+ assert parsed["subtitle"] == "I'm happy to see you!"
12
+ assert parsed["voice_instruction"] == "Speak warmly."
13
+
14
+
15
+ def test_extract_json_object_from_text_wrapper():
16
+ raw = 'Result: {"subtitle": "Hello.", "voice_instruction": "Speak clearly."} Done.'
17
+
18
+ parsed = extract_json_object(raw)
19
+
20
+ assert parsed == {
21
+ "subtitle": "Hello.",
22
+ "voice_instruction": "Speak clearly.",
23
+ }
24
+
25
+
26
+ def test_normalize_llm_output_blocks_json_subtitle():
27
+ normalized = normalize_llm_output(
28
+ {
29
+ "subtitle": '{"subtitle": "Bad nested payload"}',
30
+ "voice_instruction": "Speak warmly.",
31
+ }
32
+ )
33
+
34
+ assert normalized["subtitle"] == "I am happy to see you."
35
+ assert normalized["voice_instruction"] == "Speak warmly."
36
+
37
+
38
+ def test_safe_json_loads_falls_back_to_raw_text():
39
+ parsed = safe_json_loads("not json")
40
+
41
+ assert parsed["raw_input"] == "not json"
42
+ assert "warning" in parsed
43
+
tests/test_tts.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ from types import SimpleNamespace
3
+
4
+ import pytest
5
+
6
+ import signspeak.tts as tts_module
7
+
8
+
9
+ class FakeTTS:
10
+ def generate_custom_voice(self, text, language, speaker, instruct):
11
+ assert text == "Hello."
12
+ assert language == "English"
13
+ assert speaker == "Ryan"
14
+ assert instruct == "Speak clearly."
15
+ return [[0.0, 0.1, 0.0]], 24000
16
+
17
+
18
+ def test_generate_tts_uses_model_and_writes_file(monkeypatch, tmp_path):
19
+ writes = []
20
+
21
+ def fake_write(path, data, sample_rate):
22
+ writes.append((path, data, sample_rate))
23
+
24
+ monkeypatch.setattr(tts_module, "get_tts_model", lambda: FakeTTS())
25
+ monkeypatch.setattr(tts_module.tempfile, "gettempdir", lambda: str(tmp_path))
26
+ monkeypatch.setitem(sys.modules, "soundfile", SimpleNamespace(write=fake_write))
27
+
28
+ output_path = tts_module.generate_tts("Hello.", "English", "Ryan", "Speak clearly.")
29
+
30
+ assert output_path.startswith(str(tmp_path))
31
+ assert writes == [(output_path, [0.0, 0.1, 0.0], 24000)]
32
+
33
+
34
+ def test_generate_tts_rejects_empty_text():
35
+ with pytest.raises(ValueError):
36
+ tts_module.generate_tts("", "English", "Ryan", "Speak clearly.")
37
+