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,157 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Browser demo for the FunASR OpenAI-compatible API."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import mimetypes
|
||||
import os
|
||||
from pathlib import Path
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import uuid
|
||||
|
||||
DEFAULT_BASE_URL = "http://localhost:8000"
|
||||
DEFAULT_MODEL = "sensevoice"
|
||||
|
||||
|
||||
def request_json(url: str, timeout: float) -> dict:
|
||||
request = urllib.request.Request(url, headers={"Accept": "application/json"})
|
||||
with urllib.request.urlopen(request, timeout=timeout) as response:
|
||||
return json.loads(response.read().decode("utf-8"))
|
||||
|
||||
|
||||
def multipart_body(audio_path: Path, model: str, response_format: str) -> tuple[bytes, str]:
|
||||
boundary = f"----funasr-gradio-{uuid.uuid4().hex}"
|
||||
content_type = mimetypes.guess_type(str(audio_path))[0] or "application/octet-stream"
|
||||
parts: list[bytes] = []
|
||||
|
||||
def add_text(name: str, value: str) -> None:
|
||||
parts.append(
|
||||
(
|
||||
f"--{boundary}\r\n"
|
||||
f"Content-Disposition: form-data; name=\"{name}\"\r\n\r\n"
|
||||
f"{value}\r\n"
|
||||
).encode("utf-8")
|
||||
)
|
||||
|
||||
parts.append(
|
||||
(
|
||||
f"--{boundary}\r\n"
|
||||
f"Content-Disposition: form-data; name=\"file\"; filename=\"{audio_path.name}\"\r\n"
|
||||
f"Content-Type: {content_type}\r\n\r\n"
|
||||
).encode("utf-8")
|
||||
)
|
||||
parts.append(audio_path.read_bytes())
|
||||
parts.append(b"\r\n")
|
||||
add_text("model", model)
|
||||
add_text("response_format", response_format)
|
||||
parts.append(f"--{boundary}--\r\n".encode("utf-8"))
|
||||
return b"".join(parts), boundary
|
||||
|
||||
|
||||
def transcribe_audio(base_url: str, audio_path: str | None, model: str, response_format: str, timeout: float) -> tuple[str, str]:
|
||||
if not audio_path:
|
||||
return "", "Upload or record an audio file first."
|
||||
|
||||
base_url = base_url.rstrip("/")
|
||||
path = Path(audio_path)
|
||||
body, boundary = multipart_body(path, model, response_format)
|
||||
request = urllib.request.Request(
|
||||
f"{base_url}/v1/audio/transcriptions",
|
||||
data=body,
|
||||
method="POST",
|
||||
headers={
|
||||
"Accept": "application/json",
|
||||
"Content-Type": f"multipart/form-data; boundary={boundary}",
|
||||
"Content-Length": str(len(body)),
|
||||
},
|
||||
)
|
||||
with urllib.request.urlopen(request, timeout=timeout) as response:
|
||||
payload = json.loads(response.read().decode("utf-8"))
|
||||
|
||||
text = payload.get("text", "")
|
||||
return text, json.dumps(payload, ensure_ascii=False, indent=2)
|
||||
|
||||
|
||||
def check_service(base_url: str, timeout: float) -> str:
|
||||
base_url = base_url.rstrip("/")
|
||||
health = request_json(f"{base_url}/health", timeout)
|
||||
models = request_json(f"{base_url}/v1/models", timeout)
|
||||
return json.dumps({"health": health, "models": models}, ensure_ascii=False, indent=2)
|
||||
|
||||
|
||||
def safe_transcribe(base_url: str, audio_path: str | None, model: str, response_format: str, timeout: float) -> tuple[str, str]:
|
||||
try:
|
||||
return transcribe_audio(base_url, audio_path, model, response_format, timeout)
|
||||
except urllib.error.HTTPError as error:
|
||||
detail = error.read().decode("utf-8", errors="replace")
|
||||
return "", f"HTTP {error.code} from {error.url}: {detail}"
|
||||
except Exception as error:
|
||||
return "", f"Transcription failed: {error}"
|
||||
|
||||
|
||||
def safe_check(base_url: str, timeout: float) -> str:
|
||||
try:
|
||||
return check_service(base_url, timeout)
|
||||
except urllib.error.HTTPError as error:
|
||||
detail = error.read().decode("utf-8", errors="replace")
|
||||
return f"HTTP {error.code} from {error.url}: {detail}"
|
||||
except Exception as error:
|
||||
return f"Service check failed: {error}"
|
||||
|
||||
|
||||
def build_app(default_base_url: str, default_timeout: float):
|
||||
try:
|
||||
import gradio as gr
|
||||
except ImportError as error:
|
||||
raise SystemExit("Install Gradio first: pip install gradio") from error
|
||||
|
||||
with gr.Blocks(title="FunASR OpenAI API Demo") as demo:
|
||||
gr.Markdown("# FunASR OpenAI-Compatible API Demo")
|
||||
gr.Markdown("Start `python server.py --model sensevoice --device cuda --port 8000`, then upload or record audio here.")
|
||||
|
||||
with gr.Row():
|
||||
base_url = gr.Textbox(label="API base URL", value=default_base_url)
|
||||
model = gr.Dropdown(
|
||||
label="Model alias",
|
||||
choices=["sensevoice", "paraformer", "paraformer-en", "fun-asr-nano"],
|
||||
value=DEFAULT_MODEL,
|
||||
)
|
||||
response_format = gr.Radio(label="Response format", choices=["json", "verbose_json"], value="verbose_json")
|
||||
timeout = gr.Number(label="Timeout seconds", value=default_timeout, precision=0)
|
||||
|
||||
audio = gr.Audio(label="Audio", sources=["upload", "microphone"], type="filepath")
|
||||
with gr.Row():
|
||||
check_button = gr.Button("Check service")
|
||||
transcribe_button = gr.Button("Transcribe", variant="primary")
|
||||
|
||||
transcript = gr.Textbox(label="Transcript", lines=6)
|
||||
raw_json = gr.Code(label="Raw JSON or status", language="json")
|
||||
|
||||
check_button.click(fn=safe_check, inputs=[base_url, timeout], outputs=raw_json)
|
||||
transcribe_button.click(
|
||||
fn=safe_transcribe,
|
||||
inputs=[base_url, audio, model, response_format, timeout],
|
||||
outputs=[transcript, raw_json],
|
||||
)
|
||||
|
||||
return demo
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Run a Gradio demo for the FunASR OpenAI-compatible API")
|
||||
parser.add_argument("--base-url", default=os.getenv("BASE_URL", DEFAULT_BASE_URL), help="FunASR API base URL")
|
||||
parser.add_argument("--host", default=os.getenv("GRADIO_HOST", "127.0.0.1"), help="Gradio bind host")
|
||||
parser.add_argument("--port", type=int, default=int(os.getenv("GRADIO_PORT", "7860")), help="Gradio bind port")
|
||||
parser.add_argument("--timeout", type=float, default=float(os.getenv("TIMEOUT", "300")), help="HTTP timeout in seconds")
|
||||
parser.add_argument("--share", action="store_true", help="Create a temporary Gradio share link")
|
||||
args = parser.parse_args()
|
||||
|
||||
app = build_app(args.base_url, args.timeout)
|
||||
app.launch(server_name=args.host, server_port=args.port, share=args.share)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user