Initial commit: FunASR Speech Recognition Toolkit
Update API Documentation / build-api-docs (push) Has been cancelled
Update API Documentation / build-api-docs (push) Has been cancelled
Add complete FunASR codebase including models, runtime, and documentation.
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
import importlib.util
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
_MODULE_PATH = Path(__file__).resolve().parents[1] / "funasr" / "utils" / "postprocess_hotwords.py"
|
||||
_SPEC = importlib.util.spec_from_file_location("postprocess_hotwords", _MODULE_PATH)
|
||||
postprocess_hotwords = importlib.util.module_from_spec(_SPEC)
|
||||
sys.modules["postprocess_hotwords"] = postprocess_hotwords
|
||||
assert _SPEC.loader is not None
|
||||
_SPEC.loader.exec_module(postprocess_hotwords)
|
||||
|
||||
HotwordMatch = postprocess_hotwords.HotwordMatch
|
||||
PostprocessHotwordMatcher = postprocess_hotwords.PostprocessHotwordMatcher
|
||||
apply_postprocess_hotwords_to_results = postprocess_hotwords.apply_postprocess_hotwords_to_results
|
||||
build_postprocess_hotword_matcher = postprocess_hotwords.build_postprocess_hotword_matcher
|
||||
parse_hotword_file = postprocess_hotwords.parse_hotword_file
|
||||
parse_postprocess_hotwords = postprocess_hotwords.parse_postprocess_hotwords
|
||||
|
||||
|
||||
class TestPostprocessHotwordParsing(unittest.TestCase):
|
||||
def test_parse_list_and_dict(self):
|
||||
explicit, fuzzy = parse_postprocess_hotwords(["科大讯飞", "东方财富"])
|
||||
self.assertEqual(explicit, {})
|
||||
self.assertEqual(fuzzy, ["科大讯飞", "东方财富"])
|
||||
|
||||
explicit, fuzzy = parse_postprocess_hotwords({"科大迅飞": "科大讯飞", "东方财富": "东方财富"})
|
||||
self.assertEqual(explicit, {"科大迅飞": "科大讯飞"})
|
||||
self.assertEqual(fuzzy, ["东方财富"])
|
||||
|
||||
def test_parse_inline_mapping(self):
|
||||
explicit, fuzzy = parse_postprocess_hotwords(["撒贝你=>撒贝宁", "康辉"])
|
||||
self.assertEqual(explicit, {"撒贝你": "撒贝宁"})
|
||||
self.assertEqual(fuzzy, ["康辉"])
|
||||
|
||||
def test_parse_hotword_file(self):
|
||||
with tempfile.NamedTemporaryFile("w", encoding="utf-8", delete=False) as f:
|
||||
f.write("# comment\n")
|
||||
f.write("科大讯飞\n")
|
||||
f.write("科大迅飞=>科大讯飞\n")
|
||||
path = f.name
|
||||
|
||||
try:
|
||||
explicit, fuzzy = parse_hotword_file(path)
|
||||
self.assertEqual(explicit, {"科大迅飞": "科大讯飞"})
|
||||
self.assertEqual(fuzzy, ["科大讯飞"])
|
||||
finally:
|
||||
os.unlink(path)
|
||||
|
||||
|
||||
class TestPostprocessHotwordMatcher(unittest.TestCase):
|
||||
def test_explicit_replace(self):
|
||||
matcher = PostprocessHotwordMatcher(
|
||||
explicit_map={"撒贝你": "撒贝宁"},
|
||||
enable_fuzzy=False,
|
||||
)
|
||||
text, matches = matcher.apply_text("我非常喜欢撒贝你说的新闻")
|
||||
self.assertEqual(text, "我非常喜欢撒贝宁说的新闻")
|
||||
self.assertEqual(len(matches), 1)
|
||||
self.assertEqual(matches[0].replacement, "撒贝宁")
|
||||
self.assertEqual(matches[0].score, 1.0)
|
||||
|
||||
def test_fuzzy_replace_with_optional_deps(self):
|
||||
try:
|
||||
import pypinyin # noqa: F401
|
||||
import rapidfuzz # noqa: F401
|
||||
except ImportError:
|
||||
self.skipTest("pypinyin and rapidfuzz are required for fuzzy tests")
|
||||
|
||||
matcher = PostprocessHotwordMatcher(
|
||||
fuzzy_targets=["科大讯飞"],
|
||||
threshold=0.75,
|
||||
)
|
||||
text, matches = matcher.apply_text("科大迅飞的语音识别很强")
|
||||
self.assertIn("科大讯飞", text)
|
||||
self.assertTrue(matches)
|
||||
self.assertEqual(matches[0].replacement, "科大讯飞")
|
||||
|
||||
def test_missing_fuzzy_dependency_raises(self):
|
||||
with mock.patch.object(
|
||||
postprocess_hotwords,
|
||||
"_require_pypinyin",
|
||||
side_effect=ImportError("missing pypinyin"),
|
||||
):
|
||||
with self.assertRaises(ImportError):
|
||||
PostprocessHotwordMatcher(
|
||||
fuzzy_targets=["科大讯飞"],
|
||||
threshold=0.85,
|
||||
)
|
||||
|
||||
def test_invalid_threshold_raises(self):
|
||||
with self.assertRaises(ValueError):
|
||||
PostprocessHotwordMatcher(explicit_map={"a": "b"}, threshold=1.5)
|
||||
|
||||
def test_sentence_info_and_timestamp_preserved(self):
|
||||
matcher = PostprocessHotwordMatcher(
|
||||
explicit_map={"撒贝你": "撒贝宁"},
|
||||
enable_fuzzy=False,
|
||||
)
|
||||
result = {
|
||||
"text": "撒贝你主持节目",
|
||||
"timestamp": [[0, 100], [100, 200]],
|
||||
"sentence_info": [
|
||||
{"text": "撒贝你主持", "sentence": "撒贝你主持", "start": 0, "end": 1000},
|
||||
{"text": "节目", "sentence": "节目", "start": 1000, "end": 1500},
|
||||
],
|
||||
}
|
||||
matcher.apply_result(result, return_matches=True)
|
||||
self.assertEqual(result["text"], "撒贝宁主持节目")
|
||||
self.assertEqual(result["sentence_info"][0]["text"], "撒贝宁主持")
|
||||
self.assertEqual(result["sentence_info"][0]["sentence"], "撒贝宁主持")
|
||||
self.assertEqual(result["timestamp"], [[0, 100], [100, 200]])
|
||||
self.assertEqual(result["postprocess_hotword_matches"][0]["replacement"], "撒贝宁")
|
||||
|
||||
def test_matcher_reused_for_multiple_results(self):
|
||||
build_calls = {"count": 0}
|
||||
original_build = build_postprocess_hotword_matcher
|
||||
|
||||
def counting_build(*args, **kwargs):
|
||||
build_calls["count"] += 1
|
||||
return original_build(*args, **kwargs)
|
||||
|
||||
results = [
|
||||
{"text": "撒贝你主持", "timestamp": [1]},
|
||||
{"text": "康灰播报", "timestamp": [2]},
|
||||
]
|
||||
cfg = {
|
||||
"postprocess_hotwords": {"撒贝你": "撒贝宁", "康灰": "康辉"},
|
||||
"return_postprocess_hotword_matches": True,
|
||||
}
|
||||
with mock.patch.object(
|
||||
postprocess_hotwords,
|
||||
"build_postprocess_hotword_matcher",
|
||||
side_effect=counting_build,
|
||||
):
|
||||
updated = apply_postprocess_hotwords_to_results(results, cfg)
|
||||
|
||||
self.assertEqual(build_calls["count"], 1)
|
||||
self.assertEqual(updated[0]["text"], "撒贝宁主持")
|
||||
self.assertEqual(updated[1]["text"], "康辉播报")
|
||||
self.assertEqual(len(updated[0]["postprocess_hotword_matches"]), 1)
|
||||
|
||||
def test_noop_when_not_configured(self):
|
||||
results = [{"text": "不变", "timestamp": [1]}]
|
||||
updated = apply_postprocess_hotwords_to_results(results, {})
|
||||
self.assertIs(updated, results)
|
||||
self.assertEqual(updated[0]["text"], "不变")
|
||||
|
||||
|
||||
class TestHotwordMatch(unittest.TestCase):
|
||||
def test_as_dict(self):
|
||||
match = HotwordMatch("a", "b", 0.9, 1, 2)
|
||||
self.assertEqual(
|
||||
match.as_dict(),
|
||||
{"original": "a", "replacement": "b", "score": 0.9, "start": 1, "end": 2},
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user