mirror of
https://github.com/modelscope/FunASR.git
synced 2026-10-12 02:19:18 +00:00
Preserve speaker in OpenAI transcription segments (#3086)
* Preserve speaker in OpenAI transcription segments * Tidy OpenAI speaker response test stub
This commit is contained in:
@@ -184,6 +184,30 @@ def process_audio(audio_data, sr=16000, language=None, hotwords=None,
|
||||
}
|
||||
|
||||
|
||||
def build_openai_verbose_json(result, language=None):
|
||||
"""Build OpenAI-compatible verbose_json while preserving FunASR extensions."""
|
||||
segments = []
|
||||
for i, seg in enumerate(result["segments"]):
|
||||
item = {
|
||||
"id": i,
|
||||
"start": seg["start"],
|
||||
"end": seg["end"],
|
||||
"text": seg["text"],
|
||||
"words": seg.get("words", []),
|
||||
}
|
||||
if "speaker" in seg:
|
||||
item["speaker"] = seg["speaker"]
|
||||
segments.append(item)
|
||||
|
||||
return {
|
||||
"task": "transcribe",
|
||||
"language": language or "zh",
|
||||
"duration": result["duration"],
|
||||
"text": result["text"],
|
||||
"segments": segments,
|
||||
}
|
||||
|
||||
|
||||
# ============================================================
|
||||
# FastAPI App
|
||||
# ============================================================
|
||||
@@ -240,22 +264,7 @@ async def openai_transcriptions(
|
||||
if response_format == "text":
|
||||
return JSONResponse(content=result["text"])
|
||||
elif response_format == "verbose_json":
|
||||
return JSONResponse(content={
|
||||
"task": "transcribe",
|
||||
"language": language or "zh",
|
||||
"duration": result["duration"],
|
||||
"text": result["text"],
|
||||
"segments": [
|
||||
{
|
||||
"id": i,
|
||||
"start": seg["start"],
|
||||
"end": seg["end"],
|
||||
"text": seg["text"],
|
||||
"words": seg.get("words", []),
|
||||
}
|
||||
for i, seg in enumerate(result["segments"])
|
||||
],
|
||||
})
|
||||
return JSONResponse(content=build_openai_verbose_json(result, language=language))
|
||||
else:
|
||||
return JSONResponse(content={"text": result["text"]})
|
||||
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
import importlib.util
|
||||
import sys
|
||||
import types
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
SERVICE_PATH = (
|
||||
REPO_ROOT
|
||||
/ "examples"
|
||||
/ "industrial_data_pretraining"
|
||||
/ "fun_asr_nano"
|
||||
/ "serve_vllm.py"
|
||||
)
|
||||
|
||||
|
||||
def load_service_module(monkeypatch):
|
||||
fastapi_stub = types.ModuleType("fastapi")
|
||||
|
||||
class FastAPIStub:
|
||||
def __init__(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def on_event(self, *args, **kwargs):
|
||||
return lambda func: func
|
||||
|
||||
def post(self, *args, **kwargs):
|
||||
return lambda func: func
|
||||
|
||||
def websocket(self, *args, **kwargs):
|
||||
return lambda func: func
|
||||
|
||||
fastapi_stub.FastAPI = FastAPIStub
|
||||
fastapi_stub.File = lambda *args, **kwargs: None
|
||||
fastapi_stub.Form = lambda *args, **kwargs: None
|
||||
fastapi_stub.UploadFile = object
|
||||
fastapi_stub.WebSocket = object
|
||||
fastapi_stub.WebSocketDisconnect = Exception
|
||||
|
||||
responses_stub = types.ModuleType("fastapi.responses")
|
||||
|
||||
class JSONResponseStub:
|
||||
def __init__(self, content=None):
|
||||
self.content = content
|
||||
|
||||
responses_stub.JSONResponse = JSONResponseStub
|
||||
|
||||
funasr_stub = types.ModuleType("funasr")
|
||||
funasr_stub.AutoModel = object
|
||||
|
||||
nano_stub = types.ModuleType("funasr.models.fun_asr_nano.inference_vllm")
|
||||
nano_stub.FunASRNanoVLLM = object
|
||||
|
||||
vad_stub = types.ModuleType("funasr.models.fsmn_vad_streaming.dynamic_vad")
|
||||
vad_stub.DynamicStreamingVAD = object
|
||||
|
||||
monkeypatch.setitem(sys.modules, "fastapi", fastapi_stub)
|
||||
monkeypatch.setitem(sys.modules, "fastapi.responses", responses_stub)
|
||||
monkeypatch.setitem(sys.modules, "uvicorn", types.ModuleType("uvicorn"))
|
||||
monkeypatch.setitem(sys.modules, "soundfile", types.ModuleType("soundfile"))
|
||||
monkeypatch.setitem(sys.modules, "torch", types.ModuleType("torch"))
|
||||
monkeypatch.setitem(sys.modules, "funasr", funasr_stub)
|
||||
monkeypatch.setitem(sys.modules, "funasr.models", types.ModuleType("funasr.models"))
|
||||
monkeypatch.setitem(
|
||||
sys.modules,
|
||||
"funasr.models.fun_asr_nano",
|
||||
types.ModuleType("funasr.models.fun_asr_nano"),
|
||||
)
|
||||
monkeypatch.setitem(
|
||||
sys.modules, "funasr.models.fun_asr_nano.inference_vllm", nano_stub
|
||||
)
|
||||
monkeypatch.setitem(
|
||||
sys.modules,
|
||||
"funasr.models.fsmn_vad_streaming",
|
||||
types.ModuleType("funasr.models.fsmn_vad_streaming"),
|
||||
)
|
||||
monkeypatch.setitem(
|
||||
sys.modules, "funasr.models.fsmn_vad_streaming.dynamic_vad", vad_stub
|
||||
)
|
||||
|
||||
module_name = "serve_vllm_under_test"
|
||||
sys.modules.pop(module_name, None)
|
||||
spec = importlib.util.spec_from_file_location(module_name, SERVICE_PATH)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
assert spec.loader is not None
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def test_openai_verbose_json_keeps_segment_speaker(monkeypatch):
|
||||
module = load_service_module(monkeypatch)
|
||||
|
||||
response = module.build_openai_verbose_json(
|
||||
{
|
||||
"duration": 1.2,
|
||||
"text": "hello",
|
||||
"segments": [
|
||||
{
|
||||
"start": 0.0,
|
||||
"end": 1.2,
|
||||
"text": "hello",
|
||||
"words": [{"word": "hello", "start": 0.0, "end": 1.2}],
|
||||
"speaker": "SPK0",
|
||||
}
|
||||
],
|
||||
},
|
||||
language="en",
|
||||
)
|
||||
|
||||
assert response["segments"][0]["speaker"] == "SPK0"
|
||||
Reference in New Issue
Block a user